From c034e6cf487874d59e523ee84a8fef6c1cf82b6b Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 27 Mar 2024 18:43:36 -0700 Subject: [PATCH 001/136] Seed repo --- .devops.sh | 37 +++++++++ .gitignore | 65 ++++++++++++++- .gitlab-ci.yml | 178 ++++++++++++++++++++++++++++++++++++++++ .golangci.yml | 95 +++++++++++++++++++++ Dockerfile | 34 ++------ Makefile | 55 +++++++++++++ docker-user | 1 + scripts/build.sh | 40 +++++++++ scripts/docker-login.sh | 24 ++++++ scripts/docker.sh | 86 +++++++++++++++++++ scripts/release-tag.sh | 18 ++++ scripts/test.sh | 16 ++++ version | 1 + 13 files changed, 624 insertions(+), 26 deletions(-) create mode 100755 .devops.sh create mode 100644 .gitlab-ci.yml create mode 100644 .golangci.yml create mode 100644 Makefile create mode 100644 docker-user create mode 100755 scripts/build.sh create mode 100755 scripts/docker-login.sh create mode 100755 scripts/docker.sh create mode 100755 scripts/release-tag.sh create mode 100755 scripts/test.sh create mode 100644 version diff --git a/.devops.sh b/.devops.sh new file mode 100755 index 0000000..71f765a --- /dev/null +++ b/.devops.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +DEVOPS_UTILS_URL=${DEVOPS_UTILS_URL:-"gitlab.com/f5/nginx/tools/devops-utils.git"} +DEVOPS_UTILS_REF=${DEVOPS_UTILS_REF:-"master"} +rootdir=$(git rev-parse --show-toplevel) +devops_utils_dir="${rootdir}/.devops-utils" +git_update_file=${devops_utils_dir}/.last-git-update +ttl_seconds=$((60 * 60)) + +if [ "${CI}" == "true" ]; then + url="https://gitlab-ci-token:${CI_JOB_TOKEN}@${DEVOPS_UTILS_URL}" +else + # - change the first occurrence of "/" to ":" for local git clone + url="git@${DEVOPS_UTILS_URL/\//:}" +fi + +epoch=$(date +%s) + +# - get a local copy of devops-utils and update it when it's more than 1h old +if [ ! -d "$devops_utils_dir" ]; then + if ! git clone -q "${url}" "${devops_utils_dir}" --branch "${DEVOPS_UTILS_REF}" --depth 1; then + echo "ERROR: failed to clone devops-utils repo!" + exit 1 + fi + echo "$epoch" > "$git_update_file" +else + if [ $((epoch - $(cat "$git_update_file"))) -gt $ttl_seconds ]; then + cd "$devops_utils_dir" || exit + git fetch -q origin + git reset -q --hard origin/"${DEVOPS_UTILS_REF}" + echo "$epoch" > "$git_update_file" + cd .. + fi +fi + +# shellcheck disable=SC1090,SC1091 +source "${devops_utils_dir}/devops-core-services.sh" diff --git a/.gitignore b/.gitignore index cb5c33a..4e1c0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,67 @@ cover* tmp/ docs/tls/DESIGN.md :q -qqq \ No newline at end of file +qqq.env +.env* +!.env.example +!.allowed_clients.json +!.env.example.auth +*.db +priv/certs +priv/nginx-agent/* +!priv/nginx-agent/nginx-agent.conf.example +key-data.json +nginx-instance-manager.tar.gz +vendor/ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Output from debugger +__debug_bin + +code-quality.json +coverage/* + +# vim +*~ +*.swp + +### VisualStudioCode (from https://gitignore.io/api/VisualStudioCode) ### +.vscode/* +!.vscode/tasks.example.json +!.vscode/launch.example.json +!.vscode/extensions.json +!.vscode/KubernetesLocalProcessConfig*.yaml +*.code-workspace + +### Goland +.idea/* + +# bridge to kubernetes artifact +/KubernetesLocalProcessConfig.yaml + + +# output directory for build artifacts +build + +# output directory for test artifacts (eg. coverage report, junit xml) +results + +# devops-utils repo +.devops-utils/ + +# Ignore golang cache in CI +.go/pkg/mod + +.go-build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..08dfa9a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,178 @@ +--- +include: + - project: "f5/nginx/tools/devops-utils" + file: "/include/cxsast-ci-generic.yml" + ref: "master" + - project: "f5/nginx/tools/devops-utils" + file: "/include/devops-docker-cicd.yaml" + ref: "master" + - project: "f5/nginx/tools/devops-utils" + file: "include/devops-whitesource.yaml" + ref: "master" + - project: "f5/nginx/tools/devops-utils" + file: "/include/gitlab-sast.yml" + ref: "master" + +stages: + - lint+test+build + - release + +variables: + DEVTOOLS_IMG: ${DEVOPS_DOCKER_URL_DEFAULT}/nginx-azure-lb/nlb-devtools:latest + +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + +.import-devops-core-services: &import-devops-core-services | + source ${CI_PROJECT_DIR}/.devops.sh + +.go-cache: + variables: + GOPATH: $CI_PROJECT_DIR/.go + cache: + key: + files: + - go.mod + - go.sum + paths: + - .go/pkg/mod/ + +.go-cache-readonly: + extends: + - .go-cache + cache: + policy: pull + +.golang-private: &golang-private + - | + cat << EOF > ~/.netrc + machine gitlab.com + login gitlab-ci-token + password ${CI_JOB_TOKEN} + EOF + go env -w GOPRIVATE="gitlab.com/f5" + go env + +lint + unit-test + build: + stage: lint+test+build + image: $DEVTOOLS_IMG + extends: + - .devops-docker-cicd-large + - .go-cache + script: + - *golang-private + - | + if [ "$CI_COMMIT_BRANCH" != "$CI_DEFAULT_BRANCH" ]; then + time make lint + git diff --exit-code + time make test + fi + time make publish + coverage: '/^total:\s+\(statements\)\s+(\d+\.\d+\%)$/' + artifacts: + when: + always + paths: + - results + expire_in: 3 hours + reports: + junit: results/report.xml + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + - if: '$CI_COMMIT_BRANCH || $CI_MERGE_REQUEST_ID' + +unit-test-data-race: + stage: lint+test+build + image: $DEVTOOLS_IMG + variables: + GO_DATA_RACE: "true" + extends: + - .default-runner-large + - .go-cache-readonly + script: + - *golang-private + - time make test + coverage: '/^total:\s+\(statements\)\s+(\d+\.\d+\%)$/' + artifacts: + when: + always + paths: + - results + expire_in: 3 hours + reports: + junit: results/report.xml + rules: + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $GO_DATA_RACE == "true"' + +whitesource-scan: + stage: lint+test+build + extends: + - .default-runner + - .go-cache + - .whitesource-template-go + variables: + NESTED: "true" + WS_WSS_URL: "https://f5.whitesourcesoftware.com/agent" + WS_APIKEY: "${WS_APIKEY_NGINX}" + WS_PRODUCTNAME: "N4A" + WS_PROJECTNAME: "${CI_PROJECT_NAME}" + WS_GO_MODULES_RESOLVEDEPENDENCIES: "true" + WS_GO_RESOLVEDEPENDENCIES: "false" + WS_GENERATEPROJECTDETAILSJSON: "true" + script: + - *golang-private + - !reference [.whitesource-template-go, script] + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + artifacts: + when: always + paths: + - ${CI_PROJECT_DIR}/whitesource/ + expire_in: 1 weeks + +################################################################################ +# GitLab SAST security scanning +# CISO requires that all products be scanned statically for vulnerabilities. +# For more information, please see: +# https://gitlab.com/f5/nginx/tools/devops-utils/-/blob/master/include/gitlab-sast.md +# NOTE: please do not alter, change, modify, or overwrite job rules, job +# variables, or runtime states in order to maintain compliance to CISO +# requirements. +################################################################################ +sast: + stage: lint+test+build + +# ===================================================================================== +# CISO-required, SAST-scanning jobs involving the widely distributed checkmarx vendor +# Job starts immediately and is independent of all other jobs present in the pipeline. +# ===================================================================================== +checkmarx-scan: + stage: lint+test+build + extends: + # Please DO NOT overwrite extended job's image, gitlab-runner tags, or job rules + - .checkmarx-scan-security + variables: + # project specific variables + CX_SOURCES: "." + CX_FLOW_ZIP_EXCLUDE: "" + needs: [] + allow_failure: true + +tag: + stage: release + image: $DEVTOOLS_IMG + extends: + - .default-runner + script: + - ./scripts/release-tag.sh + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d89f3f5 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,95 @@ +# GolangCI-Lint settings + +# Disable all linters and enable the required ones +linters: + disable-all: true + + # Supported linters: https://golangci-lint.run/usage/linters/ + enable: + - errcheck + - exhaustruct + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused + - bodyclose + - dupl + - gochecknoinits + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - gosec + - lll + - misspell + - nakedret + - prealloc + - exportloopref + - stylecheck + - unconvert + - unparam + - paralleltest + - forbidigo + fast: false + +# Run options +run: + # 10 minute timeout for analysis + timeout: 10m + skip-dirs-use-default: true + skip-dirs: + - .go/pkg/mod + - pkg/spec/api # Generated code + - vendor + - vendor-fork +# Specific linter settings +linters-settings: + gocyclo: + # Minimal code complexity to report + min-complexity: 16 + govet: + # Report shadowed variables + check-shadowing: true + misspell: + # Correct spellings using locale preferences for US + locale: US + goimports: + # Put imports beginning with prefix after 3rd-party packages + local-prefixes: gitswarm.f5net.com/indigo,gitlab.com/f5 + exhaustruct: + # List of regular expressions to match struct packages and names. + # If this list is empty, all structs are tested. + # Default: [] + include: + - 'gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/pkg/token.TokenID' + - 'gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/internal/dpo/agent/certificates.CertGetRequest' + +issues: + # Exclude configuration + exclude-rules: + # Exclude gochecknoinits and gosec from running on tests files + - path: _test\.go + linters: + - gochecknoinits + - gosec + - path: test/* + linters: + - gochecknoinits + - gosec + # Exclude lll issues for long lines with go:generate + - linters: + - lll + source: "^//go:generate " + # Exclude false positive paralleltest error : Range statement for test case does not use range value in test Run + - linters: + - paralleltest + text: "does not use range value in test Run" + + # Disable maximum issues count per one linter + max-issues-per-linter: 0 + + # Disable maximum count of issues with the same text + max-same-issues: 0 diff --git a/Dockerfile b/Dockerfile index 9aa6b8c..0a12779 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,11 @@ -# Copyright 2023 f5 Inc. All rights reserved. -# Use of this source code is governed by the Apache -# license that can be found in the LICENSE file. +FROM alpine:3.14.1 AS base-certs +RUN apk update && apk add --no-cache ca-certificates -FROM golang:1.19.5-alpine3.16 AS builder +FROM scratch AS base +COPY docker-user /etc/passwd +USER 101 +COPY --from=base-certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -WORKDIR /app - -COPY go.mod go.sum ./ - -RUN go mod download - -COPY . . - -RUN go build -o nginx-loadbalancer-kubernetes ./cmd/nginx-loadbalancer-kubernetes/main.go - -FROM alpine:3.16 - -WORKDIR /opt/nginx-loadbalancer-kubernetes - -RUN adduser -u 11115 -D -H nlk - -USER nlk - -COPY --from=builder /app/nginx-loadbalancer-kubernetes . - -ENTRYPOINT ["/opt/nginx-loadbalancer-kubernetes/nginx-loadbalancer-kubernetes"] +FROM base as nlk +ENTRYPOINT ["/nlk"] +COPY build/nlk / diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4d404ac --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +# - init general vars +BUILD_DIR = build +export BUILD_DIR +RESULTS_DIR = results +export RESULTS_DIR +VERSION = $(shell bash -c 'source version; echo $$VERSION') +export VERSION + +DOCKER_REGISTRY ?= local +DOCKER_TAG ?= latest + +# - init go vars +GOPRIVATE = *.f5net.com,gitlab.com/f5 +export GOPRIVATE + +.PHONY: default tools deps fmt lint test build build.docker publish + +default: build + +tools: + @go install gotest.tools/gotestsum@latest + @go install golang.org/x/tools/cmd/goimports@latest + @go install github.com/jstemmer/go-junit-report@v1.0.0 + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.57.1 + +deps: + @go mod download + @go mod tidy + @go mod verify + +fmt: + @find . -type f -name "*.go" -exec goimports -e -w {} \+ + +lint: + @find . -type f -name "*.go" -exec goimports -e -w {} \+ + @golangci-lint run -v ./... + +test: + @./scripts/test.sh + +build: + @./scripts/build.sh + +build-linux: + @./scripts/build.sh linux + +build-linux-docker: + @./scripts/docker.sh build + +publish: build-linux build-linux-docker + @scripts/docker-login.sh + @./scripts/docker.sh publish + +clean: + rm -rf $(BUILD_DIR)/ diff --git a/docker-user b/docker-user new file mode 100644 index 0000000..65be48a --- /dev/null +++ b/docker-user @@ -0,0 +1 @@ +nginx:x:101:101:nginx:/var/cache/nginx:/sbin/nologin \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..aba9ad5 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -ex + +os="$1" + +GIT_DESCRIBE=$(git describe --long --always --dirty) +if [[ -z $CI_COMMIT_SHORT_SHA ]]; then + CI_COMMIT_SHORT_SHA=$(git rev-parse --short=8 HEAD) +fi +if [[ -z $VERSION ]]; then + VERSION=$(source version;echo "$VERSION") +fi + +if [ "$os" == "linux" ]; then + export GOOS=linux + export GOARCH=amd64 + export CGO_ENABLED=0 +fi + +mkdir -p "$BUILD_DIR" + +pkg_path="./cmd/nginx-loadbalancer-kubernetes" +BUILDPKG="gitlab.com/f5/nginx/nginxazurelb/nlk/pkg/buildinfo" + +ldflags=( + # Set the value of the string variable in importpath named name to value. + -X "'$BUILDPKG.semVer=$VERSION'" + -X "'$BUILDPKG.shortHash=$CI_COMMIT_SHORT_SHA'" + -X "'$BUILDPKG.gitDescribe=$GIT_DESCRIBE'" + -s # Omit the symbol table and debug information. + -w # Omit the DWARF symbol table. + -extldflags "'-fno-PIC'" +) + +go build \ + -v -tags "release osusergo" \ + -ldflags "${ldflags[*]}" \ + -o "${BUILD_DIR}/nlk" \ + "$pkg_path" diff --git a/scripts/docker-login.sh b/scripts/docker-login.sh new file mode 100755 index 0000000..1ae5d2f --- /dev/null +++ b/scripts/docker-login.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -eo pipefail + +rootdir=$(git rev-parse --show-toplevel) +docker_login_file=${rootdir}/.devops-utils/.last-docker-login + +# - perform a new docker login if last login was more than 1h ago +ttl_seconds=$((60 * 60)) + +epoch=$(date +%s) + +if [ -e "$docker_login_file" ] && [ $((epoch - $(cat "$docker_login_file"))) -lt $ttl_seconds ]; then + exit 0 +fi + +# shellcheck disable=1090 +source "${rootdir}/.devops.sh" +devops.docker.login > /dev/null +if [ "$CI" != "true" ]; then + devops.backend.docker.set "azure.container-registry-dev" + devops.docker.login > /dev/null +fi +echo "$epoch" > "$docker_login_file" diff --git a/scripts/docker.sh b/scripts/docker.sh new file mode 100755 index 0000000..0015f49 --- /dev/null +++ b/scripts/docker.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +set -eo pipefail + +ROOT_DIR=$(git rev-parse --show-toplevel) + +build() { + echo "building image: $image" + DOCKER_BUILDKIT=1 docker build --target "$image" \ + --label VERSION="$version" \ + --label COMMIT="${CI_COMMIT_SHORT_SHA}" \ + --label PROJECT_NAME="${CI_PROJECT_NAME}" \ + --tag "${repo}:${CI_COMMIT_REF_SLUG}" \ + --tag "${repo}:${CI_COMMIT_REF_SLUG}-$version" \ + --tag "${repo}:${CI_COMMIT_SHORT_SHA}" \ + -f "${ROOT_DIR}/Dockerfile" . +} + +publish() { + docker push "$repo:${CI_COMMIT_REF_SLUG}" + docker push "$repo:${CI_COMMIT_REF_SLUG}-$version" + docker push "$repo:${CI_COMMIT_SHORT_SHA}" + if [[ "$CI_COMMIT_REF_SLUG" == "${CI_DEFAULT_BRANCH}" ]]; then + docker tag "$repo:${CI_COMMIT_SHORT_SHA}" "$repo:latest" + docker tag "$repo:${CI_COMMIT_SHORT_SHA}" "$repo:$version" + docker push "$repo:latest" + docker push "$repo:$version" + fi +} + +init_ci_vars() { + if [ -z "$CI_COMMIT_SHORT_SHA" ]; then + CI_COMMIT_SHORT_SHA=$(git rev-parse --short=8 HEAD) + fi + if [ -z "$CI_PROJECT_NAME" ]; then + CI_PROJECT_NAME=$(basename "$ROOT_DIR") + fi + if [ -z "$CI_COMMIT_REF_SLUG" ]; then + CI_COMMIT_REF_SLUG=$( + git rev-parse --abbrev-ref HEAD | tr "[:upper:]" "[:lower:]" \ + | LANG=en_US.utf8 sed -E -e 's/[^a-zA-Z0-9]/-/g' -e 's/^-+|-+$$//g' \ + | cut -c 1-63 + ) + fi + if [ -z "$CI_DEFAULT_BRANCH" ]; then + CI_DEFAULT_BRANCH="main" + fi +} + +print_help () { + echo "Usage: $(basename "$0") " +} + +parse_args() { + if [[ "$#" -ne 1 ]]; then + print_help + exit 0 + fi + + action="$1" + + valid_actions="(build|publish)" + valid_actions_ptn="^${valid_actions}$" + if ! [[ "$action" =~ $valid_actions_ptn ]]; then + echo "Invalid action. Valid actions: $valid_actions" + print_help + exit 1 + fi +} + +# MAIN +image="nlk" +parse_args "$@" +init_ci_vars + +# shellcheck source=/dev/null +source "${ROOT_DIR}/.devops.sh" +if [ "$CI" != "true" ]; then + devops.backend.docker.set "azure.container-registry-dev" +fi +repo="${DEVOPS_DOCKER_URL}/nginx-azure-lb/${CI_PROJECT_NAME}/$image" +# shellcheck source=/dev/null +# shellcheck disable=SC2153 +version=$(source "${ROOT_DIR}/version";echo "$VERSION") + +"$action" diff --git a/scripts/release-tag.sh b/scripts/release-tag.sh new file mode 100755 index 0000000..8c6d639 --- /dev/null +++ b/scripts/release-tag.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +### This script should only run during master pipelines ### +### This script will create tags for the master commit using the current version ### + +set -eo pipefail + +if [ "$CI_COMMIT_REF_NAME" = "main" ]; then + # shellcheck source=/dev/null + version_tag=v$(source version;echo "$VERSION") + + curl -s \ + --request POST \ + --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN_RW}" \ + "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/repository/tags" \ + --form "tag_name=$version_tag" \ + --form "ref=$CI_COMMIT_SHA" +fi diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..107cee4 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -ex + +export GO_DATA_RACE=${GO_DATA_RACE:-false} +if [ "$GO_DATA_RACE" == "true" ]; then + go_flags+=("-race") +fi + +outfile="${RESULTS_DIR}/coverage.out" +mkdir -p "$RESULTS_DIR" +go_flags+=("-cover" -coverprofile="$outfile") +gotestsum --junitfile "${RESULTS_DIR}/report.xml" --format pkgname -- "${go_flags[@]}" ./... +echo "Total code coverage:" +go tool cover -func="$outfile" | grep 'total:' | tee "${RESULTS_DIR}/anybadge.out" +go tool cover -html="$outfile" -o "${RESULTS_DIR}/coverage.html" diff --git a/version b/version new file mode 100644 index 0000000..e0212c0 --- /dev/null +++ b/version @@ -0,0 +1 @@ +export VERSION="1.$(date +"%Y%m%d").${CI_PIPELINE_ID:-0}" From f1804c4b0857d650e6ddc491684fee063e4e7500 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 27 Mar 2024 18:44:57 -0700 Subject: [PATCH 002/136] Remove Github-y stuff --- .github/CODEOWNERS | 3 - .github/ISSUE_TEMPLATE/bug_report.md | 32 ------- .github/ISSUE_TEMPLATE/feature_request.md | 22 ----- .github/dependabot.yml | 9 -- .github/pull_request_template.md | 12 --- .github/workflows/build-and-sign-image.yml | 98 ---------------------- .github/workflows/run-scorecard.yml | 72 ---------------- .github/workflows/run-tests.yml | 32 ------- 8 files changed, 280 deletions(-) delete mode 100644 .github/CODEOWNERS delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/dependabot.yml delete mode 100644 .github/pull_request_template.md delete mode 100644 .github/workflows/build-and-sign-image.yml delete mode 100644 .github/workflows/run-scorecard.yml delete mode 100644 .github/workflows/run-tests.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index f330be6..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Main global owner # -##################### -* @ciroque @chrisakker diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index cc6c1d2..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +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 nginx_loadbalancer_kubernetes using -2. View output/logs/configuration on '...' -3. See error - -### Expected behavior - -A clear and concise description of what you expected to happen. - -### Your environment - -- Version of the nginx_loadbalancer_kubernetes or specific commit - -- Target deployment platform - -### Additional context - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index d27aba8..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' ---- -### Is your feature request related to a problem? Please describe - -A clear and concise description of what the problem is. Ex. I'm always frustrated when ... - -### Describe the solution you'd like - -A clear and concise description of what you want to happen. - -### Describe alternatives you've considered - -A clear and concise description of any alternative solutions or features you've considered. - -### Additional context - -Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4450376..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: "00:00" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index fad5aa1..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,12 +0,0 @@ -### 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 using one of the [supported keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-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/nginx-loadbalancer-kubernetes/blob/main/CONTRIBUTING.md) document -- [ ] If applicable, I have added tests that prove my fix is effective or that my feature works -- [ ] If applicable, I have checked that any relevant tests pass after adding my changes -- [ ] I have updated any relevant documentation ([`README.md`](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/README.md) and [`CHANGELOG.md`](https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/CHANGELOG.md)) diff --git a/.github/workflows/build-and-sign-image.yml b/.github/workflows/build-and-sign-image.yml deleted file mode 100644 index 2fbf227..0000000 --- a/.github/workflows/build-and-sign-image.yml +++ /dev/null @@ -1,98 +0,0 @@ -# This workflow will build and push a signed Docker image - -name: Build and sign image - -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build_and_sign_image: - runs-on: ubuntu-latest - permissions: - contents: write - packages: write - id-token: write - security-events: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - uses: anchore/sbom-action@v0 - with: - image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - output-file: ./nginx-loadbalancer-kubernetes-${{env.GITHUB_REF_NAME}}.spdx.json - registry-username: ${{ github.actor }} - registry-password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install cosign - uses: sigstore/cosign-installer@9614fae9e5c5eddabb09f90a270fcb487c9f7149 #v3.0.2 - with: - cosign-release: 'v1.13.1' - - - name: Log into registry ${{ env.REGISTRY }} for ${{ github.actor }} - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@9dc751fe249ad99385a2583ee0d084c400eee04e - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build Docker Image - id: docker-build-and-push - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{github.run_number}} - - - name: Sign the published Docker images - env: - COSIGN_EXPERIMENTAL: "true" - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: cosign sign "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.docker-build-and-push.outputs.digest }}" - - # NOTE: This runs statically against the latest tag in Docker Hub which was not produced by this workflow - # This should be updated once this workflow is fully implemented - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 # 0.16.0 - continue-on-error: true - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - 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@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v2.2.11 - continue-on-error: true - with: - sarif_file: 'trivy-results-${{ inputs.image }}.sarif' - sha: ${{ github.sha }} - ref: ${{ github.ref }} - - - name: Generate Release - uses: ncipollo/release-action@v1 - with: - artifacts: | - trivy-results-${{ inputs.image }}.sarif - ./nginx-loadbalancer-kubernetes-${{env.GITHUB_REF_NAME}}.spdx.json - body: | - # Release ${{env.GITHUB_REF_NAME}} - ## Changelog - ${{ steps.meta.outputs.changelog }} - generateReleaseNotes: true - makeLatest: false - name: "${{env.GITHUB_REF_NAME}}" diff --git a/.github/workflows/run-scorecard.yml b/.github/workflows/run-scorecard.yml deleted file mode 100644 index 3bbad84..0000000 --- a/.github/workflows/run-scorecard.yml +++ /dev/null @@ -1,72 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third-party and are governed by separate terms of service, privacy -# policy, and support documentation. - -name: Scorecard supply-chain security -on: - # 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: '15 14 * * 3' - push: - branches: [ "main" ] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecard analysis - runs-on: ubuntu-latest - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results and get a badge (see publish_results below). - id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read - - steps: - - name: "Checkout code" - uses: actions/checkout@v4 # v3.1.0 - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 - with: - results_file: results.sarif - results_format: sarif - # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecard on a *private* repository - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. - # repo_token: ${{ secrets.SCORECARD_TOKEN }} - - # Public repositories: - # - Publish results to OpenSSF REST API for easy access by consumers - # - Allows the repository to include the Scorecard badge. - # - See https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories: - # - `publish_results` will always be set to `false`, regardless - # of the value entered here. - publish_results: true - - # 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@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard. - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 - with: - sarif_file: results.sarif diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index 454c716..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,32 +0,0 @@ -# This workflow will build a golang project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Run tests - -on: - branch_protection_rule: - types: - - created - - push: - branches: - - main - - * - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.19 - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v ./... From d4f0824018e4ced9be31c67c49ab9955147205f1 Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 29 Mar 2024 12:12:35 -0700 Subject: [PATCH 003/136] Format code --- cmd/certificates-test-harness/main.go | 3 ++- cmd/configuration-test-harness/main.go | 3 ++- cmd/nginx-loadbalancer-kubernetes/main.go | 1 + cmd/tls-config-factory-test-harness/main.go | 3 ++- internal/application/application_common_test.go | 1 + internal/application/border_client.go | 1 + internal/application/border_client_test.go | 3 ++- internal/application/nginx_http_border_client.go | 1 + internal/application/nginx_stream_border_client.go | 1 + internal/authentication/factory.go | 1 + internal/certification/certificates_test.go | 5 +++-- internal/communication/factory.go | 5 +++-- internal/communication/factory_test.go | 3 ++- internal/communication/roundtripper_test.go | 5 +++-- internal/configuration/settings.go | 5 +++-- internal/core/event_test.go | 3 ++- internal/observation/handler.go | 1 + internal/observation/handler_test.go | 3 ++- internal/observation/watcher.go | 3 ++- internal/observation/watcher_test.go | 3 ++- internal/probation/server.go | 3 ++- internal/probation/server_test.go | 5 +++-- internal/synchronization/synchronizer.go | 1 + internal/synchronization/synchronizer_test.go | 3 ++- internal/translation/translator.go | 3 ++- internal/translation/translator_test.go | 7 ++++--- 26 files changed, 51 insertions(+), 25 deletions(-) diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go index 44d4a4e..d2b746b 100644 --- a/cmd/certificates-test-harness/main.go +++ b/cmd/certificates-test-harness/main.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" + "path/filepath" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "path/filepath" ) func main() { diff --git a/cmd/configuration-test-harness/main.go b/cmd/configuration-test-harness/main.go index 56e8b5d..5079a9d 100644 --- a/cmd/configuration-test-harness/main.go +++ b/cmd/configuration-test-harness/main.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" + "path/filepath" + configuration2 "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "path/filepath" ) func main() { diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 6936557..0731965 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -8,6 +8,7 @@ package main import ( "context" "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/observation" "github.com/nginxinc/kubernetes-nginx-ingress/internal/probation" diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index 3f46d4f..1813bd9 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -3,12 +3,13 @@ package main import ( "bufio" "fmt" + "os" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/sirupsen/logrus" - "os" ) const ( diff --git a/internal/application/application_common_test.go b/internal/application/application_common_test.go index e963d03..f7e9561 100644 --- a/internal/application/application_common_test.go +++ b/internal/application/application_common_test.go @@ -7,6 +7,7 @@ package application import ( "errors" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" ) diff --git a/internal/application/border_client.go b/internal/application/border_client.go index a5cc93e..0ab6be6 100644 --- a/internal/application/border_client.go +++ b/internal/application/border_client.go @@ -7,6 +7,7 @@ package application import ( "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/sirupsen/logrus" ) diff --git a/internal/application/border_client_test.go b/internal/application/border_client_test.go index 0b8105e..60ac41f 100644 --- a/internal/application/border_client_test.go +++ b/internal/application/border_client_test.go @@ -6,8 +6,9 @@ package application import ( - "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" "testing" + + "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" ) func TestBorderClient_CreatesHttpBorderClient(t *testing.T) { diff --git a/internal/application/nginx_http_border_client.go b/internal/application/nginx_http_border_client.go index b7657c5..78db773 100644 --- a/internal/application/nginx_http_border_client.go +++ b/internal/application/nginx_http_border_client.go @@ -7,6 +7,7 @@ package application import ( "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" nginxClient "github.com/nginxinc/nginx-plus-go-client/client" ) diff --git a/internal/application/nginx_stream_border_client.go b/internal/application/nginx_stream_border_client.go index 46cd498..a0adff0 100644 --- a/internal/application/nginx_stream_border_client.go +++ b/internal/application/nginx_stream_border_client.go @@ -7,6 +7,7 @@ package application import ( "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" nginxClient "github.com/nginxinc/nginx-plus-go-client/client" ) diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index 8b8d06e..69c5ee7 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -12,6 +12,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/sirupsen/logrus" diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go index c8edf14..d9d61ac 100644 --- a/internal/certification/certificates_test.go +++ b/internal/certification/certificates_test.go @@ -7,12 +7,13 @@ package certification import ( "context" + "testing" + "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/cache" - "testing" - "time" ) const ( diff --git a/internal/communication/factory.go b/internal/communication/factory.go index 9a3d411..8b19490 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -7,11 +7,12 @@ package communication import ( "crypto/tls" + netHttp "net/http" + "time" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/sirupsen/logrus" - netHttp "net/http" - "time" ) // NewHttpClient is a factory method to create a new Http Client with a default configuration. diff --git a/internal/communication/factory_test.go b/internal/communication/factory_test.go index f25abef..f95722d 100644 --- a/internal/communication/factory_test.go +++ b/internal/communication/factory_test.go @@ -7,9 +7,10 @@ package communication import ( "context" + "testing" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "k8s.io/client-go/kubernetes/fake" - "testing" ) func TestNewHttpClient(t *testing.T) { diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index f46fb71..0a549d6 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -8,10 +8,11 @@ package communication import ( "bytes" "context" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "k8s.io/client-go/kubernetes/fake" netHttp "net/http" "testing" + + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "k8s.io/client-go/kubernetes/fake" ) func TestNewRoundTripper(t *testing.T) { diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index d15ad84..e6bd30b 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -8,6 +8,9 @@ package configuration import ( "context" "fmt" + "strings" + "time" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" @@ -16,8 +19,6 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" - "strings" - "time" ) const ( diff --git a/internal/core/event_test.go b/internal/core/event_test.go index b3b8926..7f7448d 100644 --- a/internal/core/event_test.go +++ b/internal/core/event_test.go @@ -1,8 +1,9 @@ package core import ( - v1 "k8s.io/api/core/v1" "testing" + + v1 "k8s.io/api/core/v1" ) func TestNewEvent(t *testing.T) { diff --git a/internal/observation/handler.go b/internal/observation/handler.go index 83601b0..8e89745 100644 --- a/internal/observation/handler.go +++ b/internal/observation/handler.go @@ -7,6 +7,7 @@ package observation import ( "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go index f4a617f..e5bef3d 100644 --- a/internal/observation/handler_test.go +++ b/internal/observation/handler_test.go @@ -8,12 +8,13 @@ package observation import ( "context" "fmt" + "testing" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" v1 "k8s.io/api/core/v1" "k8s.io/client-go/util/workqueue" - "testing" ) func TestHandler_AddsEventToSynchronizer(t *testing.T) { diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 3ee9d3f..74e2d05 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -8,6 +8,8 @@ package observation import ( "errors" "fmt" + "time" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/sirupsen/logrus" @@ -16,7 +18,6 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/informers" "k8s.io/client-go/tools/cache" - "time" ) // Watcher is responsible for watching for changes to Kubernetes resources. diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index 36d64ee..7c820bf 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -7,10 +7,11 @@ package observation import ( "context" + "testing" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" "k8s.io/client-go/kubernetes" - "testing" ) func TestWatcher_MustInitialize(t *testing.T) { diff --git a/internal/probation/server.go b/internal/probation/server.go index 12b1699..ff23e33 100644 --- a/internal/probation/server.go +++ b/internal/probation/server.go @@ -7,8 +7,9 @@ package probation import ( "fmt" - "github.com/sirupsen/logrus" "net/http" + + "github.com/sirupsen/logrus" ) const ( diff --git a/internal/probation/server_test.go b/internal/probation/server_test.go index f594bff..4ea51e7 100644 --- a/internal/probation/server_test.go +++ b/internal/probation/server_test.go @@ -6,10 +6,11 @@ package probation import ( - "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" - "github.com/sirupsen/logrus" "net/http" "testing" + + "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" + "github.com/sirupsen/logrus" ) func TestHealthServer_HandleLive(t *testing.T) { diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 1061b01..5fc07f5 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -7,6 +7,7 @@ package synchronization import ( "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" "github.com/nginxinc/kubernetes-nginx-ingress/internal/communication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" diff --git a/internal/synchronization/synchronizer_test.go b/internal/synchronization/synchronizer_test.go index 315def7..ef510b8 100644 --- a/internal/synchronization/synchronizer_test.go +++ b/internal/synchronization/synchronizer_test.go @@ -8,10 +8,11 @@ package synchronization import ( "context" "fmt" + "testing" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" - "testing" ) func TestSynchronizer_NewSynchronizer(t *testing.T) { diff --git a/internal/translation/translator.go b/internal/translation/translator.go index b2d0e87..98ff368 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -7,12 +7,13 @@ package translation import ( "fmt" + "strings" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" - "strings" ) // Translate transforms event data into an intermediate format that can be consumed by the BorderClient implementations diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index 2acfd34..a393f64 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -7,12 +7,13 @@ package translation import ( "fmt" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - v1 "k8s.io/api/core/v1" "math/rand" "testing" "time" + + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" + v1 "k8s.io/api/core/v1" ) const ( From 12b2dd0083b3b1c440f2c1775e0c6907da43493f Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 29 Mar 2024 12:33:13 -0700 Subject: [PATCH 004/136] Lint code --- cmd/certificates-test-harness/doc.go | 3 +- cmd/certificates-test-harness/main.go | 2 +- cmd/nginx-loadbalancer-kubernetes/main.go | 14 ++---- cmd/tls-config-factory-test-harness/main.go | 45 ++++++++--------- doc.go | 2 +- .../application/application_common_test.go | 4 +- internal/application/application_constants.go | 7 +-- internal/application/border_client.go | 4 +- internal/application/border_client_test.go | 7 ++- internal/application/doc.go | 5 +- .../application/nginx_client_interface.go | 17 +++++-- .../application/nginx_http_border_client.go | 27 ++++++----- .../nginx_http_border_client_test.go | 26 ++++++---- .../application/nginx_stream_border_client.go | 5 +- .../nginx_stream_border_client_test.go | 12 +++-- internal/application/null_border_client.go | 3 +- .../application/null_border_client_test.go | 2 + internal/authentication/factory.go | 31 ++++++------ internal/authentication/factory_test.go | 48 +++++++++++-------- internal/certification/certificates.go | 12 ++--- internal/certification/certificates_test.go | 6 +++ internal/communication/factory.go | 14 +++--- internal/communication/factory_test.go | 12 +++-- internal/communication/roundtripper.go | 3 +- internal/communication/roundtripper_test.go | 10 +++- internal/configuration/settings.go | 38 ++++++++------- internal/configuration/tlsmodes_test.go | 2 + internal/core/event_test.go | 1 + internal/core/secret_bytes_test.go | 2 + internal/core/server_update_event.go | 15 ++++-- internal/core/server_update_event_test.go | 23 +++++---- internal/core/upstream_server.go | 3 +- internal/core/upstream_server_test.go | 1 + internal/observation/handler.go | 6 ++- internal/observation/handler_test.go | 7 ++- internal/observation/watcher.go | 28 ++++++----- internal/observation/watcher_test.go | 1 + internal/probation/check_test.go | 3 ++ internal/probation/server.go | 3 +- internal/probation/server_test.go | 6 +++ internal/synchronization/rand.go | 5 +- internal/synchronization/synchronizer.go | 31 +++++++----- internal/synchronization/synchronizer_test.go | 32 ++++++++++++- internal/translation/translator.go | 24 ++++++---- internal/translation/translator_test.go | 34 ++++++++++++- test/mocks/mock_nginx_plus_client.go | 10 +++- 46 files changed, 388 insertions(+), 208 deletions(-) diff --git a/cmd/certificates-test-harness/doc.go b/cmd/certificates-test-harness/doc.go index 538bed9..2d76fd5 100644 --- a/cmd/certificates-test-harness/doc.go +++ b/cmd/certificates-test-harness/doc.go @@ -4,7 +4,8 @@ */ /* -Package certificates_test_harness includes functionality boostrap and test the certification.Certificates implplementation. +Package certificates_test_harness includes functionality boostrap +and test the certification.Certificates implplementation. */ package main diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go index d2b746b..056d303 100644 --- a/cmd/certificates-test-harness/main.go +++ b/cmd/certificates-test-harness/main.go @@ -40,7 +40,7 @@ func run() error { return fmt.Errorf(`error occurred initializing certificates: %w`, err) } - go certificates.Run() + go certificates.Run() //nolint:errcheck <-ctx.Done() return nil diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 0731965..89f764f 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -47,20 +47,14 @@ func run() error { go settings.Run() - synchronizerWorkqueue, err := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) - if err != nil { - return fmt.Errorf(`error occurred building a workqueue: %w`, err) - } + synchronizerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) synchronizer, err := synchronization.NewSynchronizer(settings, synchronizerWorkqueue) if err != nil { return fmt.Errorf(`error initializing synchronizer: %w`, err) } - handlerWorkqueue, err := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) - if err != nil { - return fmt.Errorf(`error occurred building a workqueue: %w`, err) - } + handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue) @@ -106,9 +100,9 @@ func buildKubernetesClient() (*kubernetes.Clientset, error) { return client, nil } -func buildWorkQueue(settings configuration.WorkQueueSettings) (workqueue.RateLimitingInterface, error) { +func buildWorkQueue(settings configuration.WorkQueueSettings) workqueue.RateLimitingInterface { logrus.Debug("Watcher::buildSynchronizerWorkQueue") rateLimiter := workqueue.NewItemExponentialFailureRateLimiter(settings.RateLimiterBase, settings.RateLimiterMax) - return workqueue.NewNamedRateLimitingQueue(rateLimiter, settings.Name), nil + return workqueue.NewNamedRateLimitingQueue(rateLimiter, settings.Name) } diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index 1813bd9..51f1d1d 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "fmt" "os" "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" @@ -17,7 +16,7 @@ const ( ClientCertificateSecretKey = "nlk-tls-client-secret" ) -type TlsConfiguration struct { +type TLSConfiguration struct { Description string Settings configuration.Settings } @@ -28,11 +27,11 @@ func main() { configurations := buildConfigMap() for name, settings := range configurations { - fmt.Print("\033[H\033[2J") + logrus.Infof("\033[H\033[2J") logrus.Infof("\n\n\t*** Building TLS config for <<< %s >>>\n\n", name) - tlsConfig, err := authentication.NewTlsConfig(&settings.Settings) + tlsConfig, err := authentication.NewTLSConfig(&settings.Settings) if err != nil { panic(err) } @@ -41,41 +40,43 @@ func main() { certificateCount := 0 if tlsConfig.RootCAs != nil { - rootCaCount = len(tlsConfig.RootCAs.Subjects()) + rootCaCount = len(tlsConfig.RootCAs.Subjects()) //nolint:staticcheck } if tlsConfig.Certificates != nil { certificateCount = len(tlsConfig.Certificates) } - logrus.Infof("Successfully built TLS config: \n\tDescription: %s \n\tRootCA count: %v\n\tCertificate count: %v", settings.Description, rootCaCount, certificateCount) + logrus.Infof("Successfully built TLS config: \n\tDescription: %s \n\tRootCA count: %v\n\tCertificate count: %v", + settings.Description, rootCaCount, certificateCount, + ) - bufio.NewReader(os.Stdin).ReadBytes('\n') + _, _ = bufio.NewReader(os.Stdin).ReadBytes('\n') } - fmt.Print("\033[H\033[2J") + logrus.Infof("\033[H\033[2J") logrus.Infof("\n\n\t*** All done! ***\n\n") } -func buildConfigMap() map[string]TlsConfiguration { - configurations := make(map[string]TlsConfiguration) +func buildConfigMap() map[string]TLSConfiguration { + configurations := make(map[string]TLSConfiguration) - configurations["ss-tls"] = TlsConfiguration{ + configurations["ss-tls"] = TLSConfiguration{ Description: "Self-signed TLS requires just a CA certificate", - Settings: ssTlsConfig(), + Settings: ssTLSConfig(), } - configurations["ss-mtls"] = TlsConfiguration{ + configurations["ss-mtls"] = TLSConfiguration{ Description: "Self-signed mTLS requires a CA certificate and a client certificate", Settings: ssMtlsConfig(), } - configurations["ca-tls"] = TlsConfiguration{ + configurations["ca-tls"] = TLSConfiguration{ Description: "CA TLS requires no certificates", - Settings: caTlsConfig(), + Settings: caTLSConfig(), } - configurations["ca-mtls"] = TlsConfiguration{ + configurations["ca-mtls"] = TLSConfiguration{ Description: "CA mTLS requires a client certificate", Settings: caMtlsConfig(), } @@ -83,13 +84,13 @@ func buildConfigMap() map[string]TlsConfiguration { return configurations } -func ssTlsConfig() configuration.Settings { +func ssTLSConfig() configuration.Settings { certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ - TlsMode: configuration.SelfSignedTLS, + TLSMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, @@ -102,16 +103,16 @@ func ssMtlsConfig() configuration.Settings { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ - TlsMode: configuration.SelfSignedMutualTLS, + TLSMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, } } -func caTlsConfig() configuration.Settings { +func caTLSConfig() configuration.Settings { return configuration.Settings{ - TlsMode: configuration.CertificateAuthorityTLS, + TLSMode: configuration.CertificateAuthorityTLS, } } @@ -120,7 +121,7 @@ func caMtlsConfig() configuration.Settings { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ - TlsMode: configuration.CertificateAuthorityMutualTLS, + TLSMode: configuration.CertificateAuthorityMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, diff --git a/doc.go b/doc.go index 7c97bd2..1034f14 100644 --- a/doc.go +++ b/doc.go @@ -3,4 +3,4 @@ * Use of this source code is governed by the Apache License that can be found in the LICENSE file. */ -package kubernetes_nginx_ingress +package kubernetesnginxingress diff --git a/internal/application/application_common_test.go b/internal/application/application_common_test.go index f7e9561..c42bc04 100644 --- a/internal/application/application_common_test.go +++ b/internal/application/application_common_test.go @@ -19,11 +19,11 @@ const ( server = "server" ) -func buildTerrorizingBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) { +func buildTerrorizingBorderClient(clientType string) (Interface, error) { nginxClient := mocks.NewErroringMockClient(errors.New(`something went horribly horribly wrong`)) bc, err := NewBorderClient(clientType, nginxClient) - return bc, nginxClient, err + return bc, err } func buildBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) { diff --git a/internal/application/application_constants.go b/internal/application/application_constants.go index 0ec1826..4cb23a5 100644 --- a/internal/application/application_constants.go +++ b/internal/application/application_constants.go @@ -12,7 +12,8 @@ package application // annotations: // nginxinc.io/nlk-: // -// where is the name of the upstream in the NGINX Plus configuration and is one of the constants below. +// where is the name of the upstream in the NGINX Plus configuration +// and is one of the constants below. // // Note, this is an extensibility point. To add a Border Server client... // 1. Create a module that implements the BorderClient interface; @@ -23,6 +24,6 @@ const ( // ClientTypeNginxStream creates a NginxStreamBorderClient that uses the Stream* methods of the NGINX Plus client. ClientTypeNginxStream = "stream" - // ClientTypeNginxHttp creates an NginxHttpBorderClient that uses the HTTP* methods of the NGINX Plus client. - ClientTypeNginxHttp = "http" + // ClientTypeNginxHTTP creates an NginxHTTPBorderClient that uses the HTTP* methods of the NGINX Plus client. + ClientTypeNginxHTTP = "http" ) diff --git a/internal/application/border_client.go b/internal/application/border_client.go index 0ab6be6..a510953 100644 --- a/internal/application/border_client.go +++ b/internal/application/border_client.go @@ -35,8 +35,8 @@ func NewBorderClient(clientType string, borderClient interface{}) (Interface, er case ClientTypeNginxStream: return NewNginxStreamBorderClient(borderClient) - case ClientTypeNginxHttp: - return NewNginxHttpBorderClient(borderClient) + case ClientTypeNginxHTTP: + return NewNginxHTTPBorderClient(borderClient) default: borderClient, _ := NewNullBorderClient() diff --git a/internal/application/border_client_test.go b/internal/application/border_client_test.go index 60ac41f..8960eee 100644 --- a/internal/application/border_client_test.go +++ b/internal/application/border_client_test.go @@ -12,18 +12,20 @@ import ( ) func TestBorderClient_CreatesHttpBorderClient(t *testing.T) { + t.Parallel() borderClient := mocks.MockNginxClient{} client, err := NewBorderClient("http", borderClient) if err != nil { t.Errorf(`error creating border client: %v`, err) } - if _, ok := client.(*NginxHttpBorderClient); !ok { - t.Errorf(`expected client to be of type NginxHttpBorderClient`) + if _, ok := client.(*NginxHTTPBorderClient); !ok { + t.Errorf(`expected client to be of type NginxHTTPBorderClient`) } } func TestBorderClient_CreatesTcpBorderClient(t *testing.T) { + t.Parallel() borderClient := mocks.MockNginxClient{} client, err := NewBorderClient("stream", borderClient) if err != nil { @@ -36,6 +38,7 @@ func TestBorderClient_CreatesTcpBorderClient(t *testing.T) { } func TestBorderClient_UnknownClientType(t *testing.T) { + t.Parallel() unknownClientType := "unknown" borderClient := mocks.MockNginxClient{} client, err := NewBorderClient(unknownClientType, borderClient) diff --git a/internal/application/doc.go b/internal/application/doc.go index 34c27d0..296cb67 100644 --- a/internal/application/doc.go +++ b/internal/application/doc.go @@ -17,7 +17,7 @@ To add a Border Server client... At this time the only supported Border Servers are NGINX Plus servers. The two Border Server clients for NGINX Plus are: -- NginxHttpBorderClient: updates NGINX Plus servers using HTTP Upstream methods on the NGINX Plus API. +- NginxHTTPBorderClient: updates NGINX Plus servers using HTTP Upstream methods on the NGINX Plus API. - NginxStreamBorderClient: updates NGINX Plus servers using Stream Upstream methods on the NGINX Plus API. Both of these implementations use the NGINX Plus client module to communicate with the NGINX Plus server. @@ -27,7 +27,8 @@ Selection of the appropriate client is based on the Annotations present on the S annotations: nginxinc.io/nlk-: -where is the name of the upstream in the NGINX Plus configuration and is one of the constants in application_constants.go. +where is the name of the upstream in the NGINX Plus configuration +and is one of the constants in application_constants.go. */ package application diff --git a/internal/application/nginx_client_interface.go b/internal/application/nginx_client_interface.go index 728db1e..31a7b94 100644 --- a/internal/application/nginx_client_interface.go +++ b/internal/application/nginx_client_interface.go @@ -7,17 +7,24 @@ package application import nginxClient "github.com/nginxinc/nginx-plus-go-client/client" -// NginxClientInterface defines the functions used on the NGINX Plus client, abstracting away the full details of that client. +// NginxClientInterface defines the functions used on the NGINX Plus client, +// abstracting away the full details of that client. type NginxClientInterface interface { // DeleteStreamServer is used by the NginxStreamBorderClient. DeleteStreamServer(upstream string, server string) error // UpdateStreamServers is used by the NginxStreamBorderClient. - UpdateStreamServers(upstream string, servers []nginxClient.StreamUpstreamServer) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) + UpdateStreamServers( + upstream string, + servers []nginxClient.StreamUpstreamServer, + ) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) - // DeleteHTTPServer is used by the NginxHttpBorderClient. + // DeleteHTTPServer is used by the NginxHTTPBorderClient. DeleteHTTPServer(upstream string, server string) error - // UpdateHTTPServers is used by the NginxHttpBorderClient. - UpdateHTTPServers(upstream string, servers []nginxClient.UpstreamServer) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) + // UpdateHTTPServers is used by the NginxHTTPBorderClient. + UpdateHTTPServers( + upstream string, + servers []nginxClient.UpstreamServer, + ) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) } diff --git a/internal/application/nginx_http_border_client.go b/internal/application/nginx_http_border_client.go index 78db773..e9e754a 100644 --- a/internal/application/nginx_http_border_client.go +++ b/internal/application/nginx_http_border_client.go @@ -2,7 +2,8 @@ * Copyright 2023 F5 Inc. All rights reserved. * Use of this source code is governed by the Apache License that can be found in the LICENSE file. */ - +// dupl complains about duplicates with nginx_stream_border_client.go +//nolint:dupl package application import ( @@ -13,26 +14,26 @@ import ( ) // NginxHttpBorderClient implements the BorderClient interface for HTTP upstreams. -type NginxHttpBorderClient struct { +type NginxHTTPBorderClient struct { BorderClient nginxClient NginxClientInterface } -// NewNginxHttpBorderClient is the Factory function for creating an NginxHttpBorderClient. -func NewNginxHttpBorderClient(client interface{}) (Interface, error) { +// NewNginxHTTPBorderClient is the Factory function for creating an NewNginxHTTPBorderClient. +func NewNginxHTTPBorderClient(client interface{}) (Interface, error) { ngxClient, ok := client.(NginxClientInterface) if !ok { return nil, fmt.Errorf(`expected a NginxClientInterface, got a %v`, client) } - return &NginxHttpBorderClient{ + return &NginxHTTPBorderClient{ nginxClient: ngxClient, }, nil } // Update manages the Upstream servers for the Upstream Name given in the ServerUpdateEvent. -func (hbc *NginxHttpBorderClient) Update(event *core.ServerUpdateEvent) error { - httpUpstreamServers := asNginxHttpUpstreamServers(event.UpstreamServers) +func (hbc *NginxHTTPBorderClient) Update(event *core.ServerUpdateEvent) error { + httpUpstreamServers := asNginxHTTPUpstreamServers(event.UpstreamServers) _, _, _, err := hbc.nginxClient.UpdateHTTPServers(event.UpstreamName, httpUpstreamServers) if err != nil { return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err) @@ -42,7 +43,7 @@ func (hbc *NginxHttpBorderClient) Update(event *core.ServerUpdateEvent) error { } // Delete deletes the Upstream server for the Upstream Name given in the ServerUpdateEvent. -func (hbc *NginxHttpBorderClient) Delete(event *core.ServerUpdateEvent) error { +func (hbc *NginxHTTPBorderClient) Delete(event *core.ServerUpdateEvent) error { err := hbc.nginxClient.DeleteHTTPServer(event.UpstreamName, event.UpstreamServers[0].Host) if err != nil { return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err) @@ -52,18 +53,18 @@ func (hbc *NginxHttpBorderClient) Delete(event *core.ServerUpdateEvent) error { } // asNginxHttpUpstreamServer converts a core.UpstreamServer to a nginxClient.UpstreamServer. -func asNginxHttpUpstreamServer(server *core.UpstreamServer) nginxClient.UpstreamServer { +func asNginxHTTPUpstreamServer(server *core.UpstreamServer) nginxClient.UpstreamServer { return nginxClient.UpstreamServer{ Server: server.Host, } } -// asNginxHttpUpstreamServers converts a core.UpstreamServers to a []nginxClient.UpstreamServer. -func asNginxHttpUpstreamServers(servers core.UpstreamServers) []nginxClient.UpstreamServer { - var upstreamServers []nginxClient.UpstreamServer +// asNginxHTTPUpstreamServers converts a core.UpstreamServers to a []nginxClient.UpstreamServer. +func asNginxHTTPUpstreamServers(servers core.UpstreamServers) []nginxClient.UpstreamServer { + upstreamServers := []nginxClient.UpstreamServer{} for _, server := range servers { - upstreamServers = append(upstreamServers, asNginxHttpUpstreamServer(server)) + upstreamServers = append(upstreamServers, asNginxHTTPUpstreamServer(server)) } return upstreamServers diff --git a/internal/application/nginx_http_border_client_test.go b/internal/application/nginx_http_border_client_test.go index defc2ef..039b4ec 100644 --- a/internal/application/nginx_http_border_client_test.go +++ b/internal/application/nginx_http_border_client_test.go @@ -3,6 +3,9 @@ * Use of this source code is governed by the Apache License that can be found in the LICENSE file. */ +// dupl complains about duplicates with nginx_stream_border_client_test.go +// +//nolint:dupl package application import ( @@ -10,8 +13,9 @@ import ( ) func TestHttpBorderClient_Delete(t *testing.T) { - event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxHttp) - borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxHttp) + t.Parallel() + event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxHTTP) + borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxHTTP) if err != nil { t.Fatalf(`error occurred creating a new border client: %v`, err) } @@ -27,8 +31,9 @@ func TestHttpBorderClient_Delete(t *testing.T) { } func TestHttpBorderClient_Update(t *testing.T) { - event := buildServerUpdateEvent(createEventType, ClientTypeNginxHttp) - borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxHttp) + t.Parallel() + event := buildServerUpdateEvent(createEventType, ClientTypeNginxHTTP) + borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxHTTP) if err != nil { t.Fatalf(`error occurred creating a new border client: %v`, err) } @@ -44,16 +49,18 @@ func TestHttpBorderClient_Update(t *testing.T) { } func TestHttpBorderClient_BadNginxClient(t *testing.T) { + t.Parallel() var emptyInterface interface{} - _, err := NewBorderClient(ClientTypeNginxHttp, emptyInterface) + _, err := NewBorderClient(ClientTypeNginxHTTP, emptyInterface) if err == nil { t.Fatalf(`expected an error to occur when creating a new border client`) } } func TestHttpBorderClient_DeleteReturnsError(t *testing.T) { - event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxHttp) - borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxHttp) + t.Parallel() + event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxHTTP) + borderClient, err := buildTerrorizingBorderClient(ClientTypeNginxHTTP) if err != nil { t.Fatalf(`error occurred creating a new border client: %v`, err) } @@ -66,8 +73,9 @@ func TestHttpBorderClient_DeleteReturnsError(t *testing.T) { } func TestHttpBorderClient_UpdateReturnsError(t *testing.T) { - event := buildServerUpdateEvent(createEventType, ClientTypeNginxHttp) - borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxHttp) + t.Parallel() + event := buildServerUpdateEvent(createEventType, ClientTypeNginxHTTP) + borderClient, err := buildTerrorizingBorderClient(ClientTypeNginxHTTP) if err != nil { t.Fatalf(`error occurred creating a new border client: %v`, err) } diff --git a/internal/application/nginx_stream_border_client.go b/internal/application/nginx_stream_border_client.go index a0adff0..19982b4 100644 --- a/internal/application/nginx_stream_border_client.go +++ b/internal/application/nginx_stream_border_client.go @@ -2,7 +2,8 @@ * Copyright 2023 F5 Inc. All rights reserved. * Use of this source code is governed by the Apache License that can be found in the LICENSE file. */ - +// dupl complains about duplicates with nginx_http_border_client.go +//nolint:dupl package application import ( @@ -58,7 +59,7 @@ func asNginxStreamUpstreamServer(server *core.UpstreamServer) nginxClient.Stream } func asNginxStreamUpstreamServers(servers core.UpstreamServers) []nginxClient.StreamUpstreamServer { - var upstreamServers []nginxClient.StreamUpstreamServer + upstreamServers := []nginxClient.StreamUpstreamServer{} for _, server := range servers { upstreamServers = append(upstreamServers, asNginxStreamUpstreamServer(server)) diff --git a/internal/application/nginx_stream_border_client_test.go b/internal/application/nginx_stream_border_client_test.go index ddcb346..c86a776 100644 --- a/internal/application/nginx_stream_border_client_test.go +++ b/internal/application/nginx_stream_border_client_test.go @@ -2,7 +2,8 @@ * Copyright 2023 F5 Inc. All rights reserved. * Use of this source code is governed by the Apache License that can be found in the LICENSE file. */ - +// dupl complains about duplicates with nginx_http_border_client_test.go +//nolint:dupl package application import ( @@ -10,6 +11,7 @@ import ( ) func TestTcpBorderClient_Delete(t *testing.T) { + t.Parallel() event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxStream) borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxStream) if err != nil { @@ -27,6 +29,7 @@ func TestTcpBorderClient_Delete(t *testing.T) { } func TestTcpBorderClient_Update(t *testing.T) { + t.Parallel() event := buildServerUpdateEvent(createEventType, ClientTypeNginxStream) borderClient, nginxClient, err := buildBorderClient(ClientTypeNginxStream) if err != nil { @@ -44,6 +47,7 @@ func TestTcpBorderClient_Update(t *testing.T) { } func TestTcpBorderClient_BadNginxClient(t *testing.T) { + t.Parallel() var emptyInterface interface{} _, err := NewBorderClient(ClientTypeNginxStream, emptyInterface) if err == nil { @@ -52,8 +56,9 @@ func TestTcpBorderClient_BadNginxClient(t *testing.T) { } func TestTcpBorderClient_DeleteReturnsError(t *testing.T) { + t.Parallel() event := buildServerUpdateEvent(deletedEventType, ClientTypeNginxStream) - borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxStream) + borderClient, err := buildTerrorizingBorderClient(ClientTypeNginxStream) if err != nil { t.Fatalf(`error occurred creating a new border client: %v`, err) } @@ -66,8 +71,9 @@ func TestTcpBorderClient_DeleteReturnsError(t *testing.T) { } func TestTcpBorderClient_UpdateReturnsError(t *testing.T) { + t.Parallel() event := buildServerUpdateEvent(createEventType, ClientTypeNginxStream) - borderClient, _, err := buildTerrorizingBorderClient(ClientTypeNginxStream) + borderClient, err := buildTerrorizingBorderClient(ClientTypeNginxStream) if err != nil { t.Fatalf(`error occurred creating a new border client: %v`, err) } diff --git a/internal/application/null_border_client.go b/internal/application/null_border_client.go index 8370fe0..b59ca22 100644 --- a/internal/application/null_border_client.go +++ b/internal/application/null_border_client.go @@ -11,7 +11,8 @@ import ( ) // NullBorderClient is a BorderClient that does nothing. -// / It serves only to prevent a panic if the BorderClient is not set correctly and errors from the factory methods are ignored. +// It serves only to prevent a panic if the BorderClient +// is not set correctly and errors from the factory methods are ignored. type NullBorderClient struct { } diff --git a/internal/application/null_border_client_test.go b/internal/application/null_border_client_test.go index 42e9dfb..f973949 100644 --- a/internal/application/null_border_client_test.go +++ b/internal/application/null_border_client_test.go @@ -8,6 +8,7 @@ package application import "testing" func TestNullBorderClient_Delete(t *testing.T) { + t.Parallel() client := NullBorderClient{} err := client.Delete(nil) if err != nil { @@ -16,6 +17,7 @@ func TestNullBorderClient_Delete(t *testing.T) { } func TestNullBorderClient_Update(t *testing.T) { + t.Parallel() client := NullBorderClient{} err := client.Update(nil) if err != nil { diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index 69c5ee7..32add62 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -18,37 +18,38 @@ import ( "github.com/sirupsen/logrus" ) -func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { - logrus.Debugf("authentication::NewTlsConfig Creating TLS config for mode: '%s'", settings.TlsMode) - switch settings.TlsMode { +func NewTLSConfig(settings *configuration.Settings) (*tls.Config, error) { + logrus.Debugf("authentication::NewTLSConfig Creating TLS config for mode: '%s'", settings.TLSMode) + switch settings.TLSMode { case configuration.NoTLS: - return buildBasicTlsConfig(true), nil + return buildBasicTLSConfig(true), nil case configuration.SelfSignedTLS: // needs ca cert - return buildSelfSignedTlsConfig(settings.Certificates) + return buildSelfSignedTLSConfig(settings.Certificates) case configuration.SelfSignedMutualTLS: // needs ca cert and client cert return buildSelfSignedMtlsConfig(settings.Certificates) case configuration.CertificateAuthorityTLS: // needs nothing - return buildBasicTlsConfig(false), nil + return buildBasicTLSConfig(false), nil case configuration.CertificateAuthorityMutualTLS: // needs client cert - return buildCaTlsConfig(settings.Certificates) + return buildCATLSConfig(settings.Certificates) default: - return nil, fmt.Errorf("unknown TLS mode: %s", settings.TlsMode) + return nil, fmt.Errorf("unknown TLS mode: %s", settings.TLSMode) } } -func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { +func buildSelfSignedTLSConfig(certificates *certification.Certificates) (*tls.Config, error) { logrus.Debug("authentication::buildSelfSignedTlsConfig Building self-signed TLS config") certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err } + //nolint:gosec return &tls.Config{ InsecureSkipVerify: false, RootCAs: certPool, @@ -68,6 +69,7 @@ func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.C } logrus.Debugf("buildSelfSignedMtlsConfig Certificate: %v", certificate) + //nolint:gosec return &tls.Config{ InsecureSkipVerify: false, RootCAs: certPool, @@ -76,20 +78,21 @@ func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.C }, nil } -func buildBasicTlsConfig(skipVerify bool) *tls.Config { - logrus.Debugf("authentication::buildBasicTlsConfig skipVerify(%v)", skipVerify) +func buildBasicTLSConfig(skipVerify bool) *tls.Config { + logrus.Debugf("authentication::buildBasicTLSConfig skipVerify(%v)", skipVerify) return &tls.Config{ - InsecureSkipVerify: skipVerify, + InsecureSkipVerify: skipVerify, //nolint:gosec } } -func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("authentication::buildCaTlsConfig") +func buildCATLSConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("authentication::buildCATLSConfig") certificate, err := buildCertificates(certificates.GetClientCertificate()) if err != nil { return nil, err } + //nolint:gosec return &tls.Config{ InsecureSkipVerify: false, Certificates: []tls.Certificate{certificate}, diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go index a535200..e9015c0 100644 --- a/internal/authentication/factory_test.go +++ b/internal/authentication/factory_test.go @@ -19,9 +19,10 @@ const ( ) func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { + t.Parallel() settings := configuration.Settings{} - tlsConfig, err := NewTlsConfig(&settings) + tlsConfig, err := NewTLSConfig(&settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -36,11 +37,12 @@ func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { } func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.SelfSignedTLS, + TLSMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -48,7 +50,7 @@ func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { }, } - tlsConfig, err := NewTlsConfig(&settings) + tlsConfig, err := NewTLSConfig(&settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -71,17 +73,18 @@ func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { } func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.SelfSignedTLS, + TLSMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, } - _, err := NewTlsConfig(&settings) + _, err := NewTLSConfig(&settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -92,11 +95,12 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { } func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) settings := configuration.Settings{ - TlsMode: configuration.SelfSignedTLS, + TLSMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -104,7 +108,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) }, } - _, err := NewTlsConfig(&settings) + _, err := NewTLSConfig(&settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -115,12 +119,13 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) } func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.SelfSignedMutualTLS, + TLSMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -128,7 +133,7 @@ func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { }, } - tlsConfig, err := NewTlsConfig(&settings) + tlsConfig, err := NewTLSConfig(&settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -151,18 +156,19 @@ func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { } func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.SelfSignedMutualTLS, + TLSMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, } - _, err := NewTlsConfig(&settings) + _, err := NewTLSConfig(&settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -173,12 +179,13 @@ func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { } func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.SelfSignedMutualTLS, + TLSMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -186,7 +193,7 @@ func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { }, } - _, err := NewTlsConfig(&settings) + _, err := NewTLSConfig(&settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -197,11 +204,12 @@ func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { } func TestTlsFactory_CaTlsMode(t *testing.T) { + t.Parallel() settings := configuration.Settings{ - TlsMode: configuration.CertificateAuthorityTLS, + TLSMode: configuration.CertificateAuthorityTLS, } - tlsConfig, err := NewTlsConfig(&settings) + tlsConfig, err := NewTLSConfig(&settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -224,11 +232,12 @@ func TestTlsFactory_CaTlsMode(t *testing.T) { } func TestTlsFactory_CaMtlsMode(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.CertificateAuthorityMutualTLS, + TLSMode: configuration.CertificateAuthorityMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -236,7 +245,7 @@ func TestTlsFactory_CaMtlsMode(t *testing.T) { }, } - tlsConfig, err := NewTlsConfig(&settings) + tlsConfig, err := NewTLSConfig(&settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -259,18 +268,19 @@ func TestTlsFactory_CaMtlsMode(t *testing.T) { } func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { + t.Parallel() certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) settings := configuration.Settings{ - TlsMode: configuration.CertificateAuthorityMutualTLS, + TLSMode: configuration.CertificateAuthorityMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, } - _, err := NewTlsConfig(&settings) + _, err := NewTLSConfig(&settings) if err == nil { t.Fatalf(`Expected an error`) } diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index 53bd843..3ecbc46 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -2,7 +2,8 @@ * Copyright 2023 F5 Inc. All rights reserved. * Use of this source code is governed by the Apache License that can be found in the LICENSE file. * - * Establishes a Watcher for the Kubernetes Secrets that contain the various certificates and keys used to generate a tls.Config object; + * Establishes a Watcher for the Kubernetes Secrets that contain the various certificates + * and keys used to generate a tls.Config object; * exposes the certificates and keys. */ @@ -86,10 +87,7 @@ func (c *Certificates) Initialize() error { c.Certificates = make(map[string]map[string]core.SecretBytes) - informer, err := c.buildInformer() - if err != nil { - return fmt.Errorf(`error occurred building an informer: %w`, err) - } + informer := c.buildInformer() c.informer = informer @@ -116,14 +114,14 @@ func (c *Certificates) Run() error { return nil } -func (c *Certificates) buildInformer() (cache.SharedInformer, error) { +func (c *Certificates) buildInformer() cache.SharedInformer { logrus.Debug("Certificates::buildInformer") options := informers.WithNamespace(SecretsNamespace) factory := informers.NewSharedInformerFactoryWithOptions(c.k8sClient, 0, options) informer := factory.Core().V1().Secrets().Informer() - return informer, nil + return informer } func (c *Certificates) initializeEventHandlers() error { diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go index d9d61ac..89b21bf 100644 --- a/internal/certification/certificates_test.go +++ b/internal/certification/certificates_test.go @@ -21,6 +21,7 @@ const ( ) func TestNewCertificate(t *testing.T) { + t.Parallel() ctx := context.Background() certificates := NewCertificates(ctx, nil) @@ -31,6 +32,7 @@ func TestNewCertificate(t *testing.T) { } func TestCertificates_Initialize(t *testing.T) { + t.Parallel() certificates := NewCertificates(context.Background(), nil) err := certificates.Initialize() @@ -40,6 +42,7 @@ func TestCertificates_Initialize(t *testing.T) { } func TestCertificates_RunWithoutInitialize(t *testing.T) { + t.Parallel() certificates := NewCertificates(context.Background(), nil) err := certificates.Run() @@ -53,6 +56,7 @@ func TestCertificates_RunWithoutInitialize(t *testing.T) { } func TestCertificates_EmptyCertificates(t *testing.T) { + t.Parallel() certificates := NewCertificates(context.Background(), nil) err := certificates.Initialize() @@ -75,6 +79,7 @@ func TestCertificates_EmptyCertificates(t *testing.T) { } func TestCertificates_ExerciseHandlers(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -86,6 +91,7 @@ func TestCertificates_ExerciseHandlers(t *testing.T) { certificates.CaCertificateSecretKey = CaCertificateSecretKey + //nolint:govet,staticcheck go func() { err := certificates.Run() if err != nil { diff --git a/internal/communication/factory.go b/internal/communication/factory.go index 8b19490..40bf02a 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -15,12 +15,12 @@ import ( "github.com/sirupsen/logrus" ) -// NewHttpClient is a factory method to create a new Http Client with a default configuration. +// NewHTTPClient is a factory method to create a new Http Client with a default configuration. // RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, // the Headers are configured for JSON. -func NewHttpClient(settings *configuration.Settings) (*netHttp.Client, error) { +func NewHTTPClient(settings *configuration.Settings) (*netHttp.Client, error) { headers := NewHeaders() - tlsConfig := NewTlsConfig(settings) + tlsConfig := NewTLSConfig(settings) transport := NewTransport(tlsConfig) roundTripper := NewRoundTripper(headers, transport) @@ -40,13 +40,13 @@ func NewHeaders() []string { } } -// NewTlsConfig is a factory method to create a new basic Tls Config. +// NewTLSConfig is a factory method to create a new basic Tls Config. // More attention should be given to the use of `InsecureSkipVerify: true`, as it is not recommended for production use. -func NewTlsConfig(settings *configuration.Settings) *tls.Config { - tlsConfig, err := authentication.NewTlsConfig(settings) +func NewTLSConfig(settings *configuration.Settings) *tls.Config { + tlsConfig, err := authentication.NewTLSConfig(settings) if err != nil { logrus.Warnf("Failed to create TLS config: %v", err) - return &tls.Config{InsecureSkipVerify: true} + return &tls.Config{InsecureSkipVerify: true} //nolint:gosec } return tlsConfig diff --git a/internal/communication/factory_test.go b/internal/communication/factory_test.go index f95722d..375da3b 100644 --- a/internal/communication/factory_test.go +++ b/internal/communication/factory_test.go @@ -13,10 +13,14 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -func TestNewHttpClient(t *testing.T) { +func TestNewHTTPClient(t *testing.T) { + t.Parallel() k8sClient := fake.NewSimpleClientset() settings, err := configuration.NewSettings(context.Background(), k8sClient) - client, err := NewHttpClient(settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + client, err := NewHTTPClient(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) @@ -28,6 +32,7 @@ func TestNewHttpClient(t *testing.T) { } func TestNewHeaders(t *testing.T) { + t.Parallel() headers := NewHeaders() if headers == nil { @@ -48,9 +53,10 @@ func TestNewHeaders(t *testing.T) { } func TestNewTransport(t *testing.T) { + t.Parallel() k8sClient := fake.NewSimpleClientset() settings, _ := configuration.NewSettings(context.Background(), k8sClient) - config := NewTlsConfig(settings) + config := NewTLSConfig(settings) transport := NewTransport(config) if transport == nil { diff --git a/internal/communication/roundtripper.go b/internal/communication/roundtripper.go index 3781c62..58de6f0 100644 --- a/internal/communication/roundtripper.go +++ b/internal/communication/roundtripper.go @@ -7,7 +7,6 @@ package communication import ( "net/http" - netHttp "net/http" "strings" ) @@ -18,7 +17,7 @@ type RoundTripper struct { } // NewRoundTripper is a factory method to create a new RoundTripper. -func NewRoundTripper(headers []string, transport *netHttp.Transport) *RoundTripper { +func NewRoundTripper(headers []string, transport *http.Transport) *RoundTripper { return &RoundTripper{ Headers: headers, RoundTripper: transport, diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index 0a549d6..ff6d5c4 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -16,10 +16,11 @@ import ( ) func TestNewRoundTripper(t *testing.T) { + t.Parallel() k8sClient := fake.NewSimpleClientset() settings, _ := configuration.NewSettings(context.Background(), k8sClient) headers := NewHeaders() - transport := NewTransport(NewTlsConfig(settings)) + transport := NewTransport(NewTLSConfig(settings)) roundTripper := NewRoundTripper(headers, transport) if roundTripper == nil { @@ -48,10 +49,14 @@ func TestNewRoundTripper(t *testing.T) { } func TestRoundTripperRoundTrip(t *testing.T) { + t.Parallel() k8sClient := fake.NewSimpleClientset() settings, err := configuration.NewSettings(context.Background(), k8sClient) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } headers := NewHeaders() - transport := NewTransport(NewTlsConfig(settings)) + transport := NewTransport(NewTLSConfig(settings)) roundTripper := NewRoundTripper(headers, transport) request, err := NewRequest("GET", "http://example.com", nil) @@ -70,6 +75,7 @@ func TestRoundTripperRoundTrip(t *testing.T) { if response == nil { t.Fatalf(`response should not be nil`) } + defer response.Body.Close() headerLen := len(response.Header) if headerLen <= 2 { diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index e6bd30b..05c3690 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -46,7 +46,8 @@ const ( // There are two work queues in the application: // 1. nlk-handler queue, used to move messages between the Watcher and the Handler. // 2. nlk-synchronizer queue, used to move message between the Handler and the Synchronizer. -// The queues are NamedDelayingQueue objects that use an ItemExponentialFailureRateLimiter as the underlying rate limiter. +// The queues are NamedDelayingQueue objects that use an ItemExponentialFailureRateLimiter +// as the underlying rate limiter. type WorkQueueSettings struct { // Name is the name of the queue. Name string @@ -110,8 +111,9 @@ type Settings struct { // NginxPlusHosts is a list of Nginx Plus hosts that will be used to update the Border Servers. NginxPlusHosts []string - // TlsMode is the value used to determine which of the five TLS modes will be used to communicate with the Border Servers (see: ../../docs/tls/README.md). - TlsMode TLSMode + // TlsMode is the value used to determine which of the five TLS modes will be used to communicate + // with the Border Servers (see: ../../docs/tls/README.md). + TLSMode TLSMode // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. Certificates *certification.Certificates @@ -140,7 +142,7 @@ func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings settings := &Settings{ Context: ctx, K8sClient: k8sClient, - TlsMode: NoTLS, + TLSMode: NoTLS, Certificates: nil, Handler: HandlerSettings{ RetryCount: 5, @@ -187,10 +189,12 @@ func (s *Settings) Initialize() error { s.Certificates = certificates - go certificates.Run() + certificates.Run() //nolint:errcheck logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieving nlk-config ConfigMap") - configMap, err := s.K8sClient.CoreV1().ConfigMaps(ConfigMapsNamespace).Get(s.Context, "nlk-config", metav1.GetOptions{}) + configMap, err := s.K8sClient.CoreV1().ConfigMaps(ConfigMapsNamespace).Get( + s.Context, "nlk-config", metav1.GetOptions{}, + ) if err != nil { return err } @@ -198,10 +202,7 @@ func (s *Settings) Initialize() error { s.handleUpdateEvent(nil, configMap) logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieved nlk-config ConfigMap") - informer, err := s.buildInformer() - if err != nil { - return fmt.Errorf(`error occurred building ConfigMap informer: %w`, err) - } + informer := s.buildInformer() s.informer = informer @@ -213,7 +214,7 @@ func (s *Settings) Initialize() error { return nil } -// Run starts the SharedInformer and waits for the Context to be cancelled. +// Run starts the SharedInformer and waits for the Context to be canceled. func (s *Settings) Run() { logrus.Debug("Settings::Run") @@ -224,12 +225,12 @@ func (s *Settings) Run() { <-s.Context.Done() } -func (s *Settings) buildInformer() (cache.SharedInformer, error) { +func (s *Settings) buildInformer() cache.SharedInformer { options := informers.WithNamespace(ConfigMapsNamespace) factory := informers.NewSharedInformerFactoryWithOptions(s.K8sClient, ResyncPeriod, options) informer := factory.Core().V1().ConfigMaps().Informer() - return informer, nil + return informer } func (s *Settings) initializeEventListeners() error { @@ -283,12 +284,15 @@ func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { logrus.Warnf("Settings::handleUpdateEvent: nginx-hosts key not found in ConfigMap") } - tlsMode, err := validateTlsMode(configMap) + tlsMode, err := validateTLSMode(configMap) if err != nil { // NOTE: the TLSMode defaults to NoTLS on startup, or the last known good value if previously set. - logrus.Errorf("There was an error with the configured TLS Mode. TLS Mode has NOT been changed. The current mode is: '%v'. Error: %v. ", s.TlsMode, err) + logrus.Errorf( + "Error with configured TLS Mode. TLS Mode has NOT been changed. The current mode is: '%v'. Error: %v. ", + s.TLSMode, err, + ) } else { - s.TlsMode = tlsMode + s.TLSMode = tlsMode } caCertificateSecretKey, found := configMap.Data["ca-certificate"] @@ -314,7 +318,7 @@ func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) } -func validateTlsMode(configMap *corev1.ConfigMap) (TLSMode, error) { +func validateTLSMode(configMap *corev1.ConfigMap) (TLSMode, error) { tlsConfigMode, tlsConfigModeFound := configMap.Data["tls-mode"] if !tlsConfigModeFound { return NoTLS, fmt.Errorf(`tls-mode key not found in ConfigMap`) diff --git a/internal/configuration/tlsmodes_test.go b/internal/configuration/tlsmodes_test.go index 62abf96..d849cd9 100644 --- a/internal/configuration/tlsmodes_test.go +++ b/internal/configuration/tlsmodes_test.go @@ -10,6 +10,7 @@ import ( ) func Test_String(t *testing.T) { + t.Parallel() mode := NoTLS.String() if mode != "no-tls" { t.Errorf("Expected TLSModeNoTLS to be 'no-tls', got '%s'", mode) @@ -42,6 +43,7 @@ func Test_String(t *testing.T) { } func Test_TLSModeMap(t *testing.T) { + t.Parallel() mode := TLSModeMap["no-tls"] if mode != NoTLS { t.Errorf("Expected TLSModeMap['no-tls'] to be TLSModeNoTLS, got '%d'", mode) diff --git a/internal/core/event_test.go b/internal/core/event_test.go index 7f7448d..662eb8f 100644 --- a/internal/core/event_test.go +++ b/internal/core/event_test.go @@ -7,6 +7,7 @@ import ( ) func TestNewEvent(t *testing.T) { + t.Parallel() expectedType := Created expectedService := &v1.Service{} expectedPreviousService := &v1.Service{} diff --git a/internal/core/secret_bytes_test.go b/internal/core/secret_bytes_test.go index 8dd8024..5e6bc3f 100644 --- a/internal/core/secret_bytes_test.go +++ b/internal/core/secret_bytes_test.go @@ -12,6 +12,7 @@ import ( ) func TestSecretBytesToString(t *testing.T) { + t.Parallel() sensitive := SecretBytes([]byte("If you can see this we have a problem")) expected := "foo [REDACTED] bar" @@ -22,6 +23,7 @@ func TestSecretBytesToString(t *testing.T) { } func TestSecretBytesToJSON(t *testing.T) { + t.Parallel() sensitive, _ := json.Marshal(SecretBytes([]byte("If you can see this we have a problem"))) expected := `foo "[REDACTED]" bar` result := fmt.Sprintf("foo %v bar", string(sensitive)) diff --git a/internal/core/server_update_event.go b/internal/core/server_update_event.go index f3961ea..0bc8868 100644 --- a/internal/core/server_update_event.go +++ b/internal/core/server_update_event.go @@ -15,7 +15,7 @@ type ServerUpdateEvent struct { ClientType string // Id is the unique identifier for this event. - Id string + ID string // NginxHost is the host name of the NGINX Plus instance that should handle this event. NginxHost string @@ -34,7 +34,12 @@ type ServerUpdateEvent struct { type ServerUpdateEvents = []*ServerUpdateEvent // NewServerUpdateEvent creates a new ServerUpdateEvent. -func NewServerUpdateEvent(eventType EventType, upstreamName string, clientType string, upstreamServers UpstreamServers) *ServerUpdateEvent { +func NewServerUpdateEvent( + eventType EventType, + upstreamName string, + clientType string, + upstreamServers UpstreamServers, +) *ServerUpdateEvent { return &ServerUpdateEvent{ ClientType: clientType, Type: eventType, @@ -43,11 +48,11 @@ func NewServerUpdateEvent(eventType EventType, upstreamName string, clientType s } } -// ServerUpdateEventWithIdAndHost creates a new ServerUpdateEvent with the specified Id and Host. -func ServerUpdateEventWithIdAndHost(event *ServerUpdateEvent, id string, nginxHost string) *ServerUpdateEvent { +// ServerUpdateEventWithIDAndHost creates a new ServerUpdateEvent with the specified Id and Host. +func ServerUpdateEventWithIDAndHost(event *ServerUpdateEvent, id string, nginxHost string) *ServerUpdateEvent { return &ServerUpdateEvent{ ClientType: event.ClientType, - Id: id, + ID: id, NginxHost: nginxHost, Type: event.Type, UpstreamName: event.UpstreamName, diff --git a/internal/core/server_update_event_test.go b/internal/core/server_update_event_test.go index a891e23..9f36002 100644 --- a/internal/core/server_update_event_test.go +++ b/internal/core/server_update_event_test.go @@ -14,32 +14,34 @@ const clientType = "clientType" var emptyUpstreamServers UpstreamServers func TestServerUpdateEventWithIdAndHost(t *testing.T) { + t.Parallel() event := NewServerUpdateEvent(Created, "upstream", clientType, emptyUpstreamServers) - if event.Id != "" { - t.Errorf("expected empty Id, got %s", event.Id) + if event.ID != "" { + t.Errorf("expected empty ID, got %s", event.ID) } if event.NginxHost != "" { t.Errorf("expected empty NginxHost, got %s", event.NginxHost) } - eventWithIdAndHost := ServerUpdateEventWithIdAndHost(event, "id", "host") + eventWithIDAndHost := ServerUpdateEventWithIDAndHost(event, "id", "host") - if eventWithIdAndHost.Id != "id" { - t.Errorf("expected Id to be 'id', got %s", eventWithIdAndHost.Id) + if eventWithIDAndHost.ID != "id" { + t.Errorf("expected Id to be 'id', got %s", eventWithIDAndHost.ID) } - if eventWithIdAndHost.NginxHost != "host" { - t.Errorf("expected NginxHost to be 'host', got %s", eventWithIdAndHost.NginxHost) + if eventWithIDAndHost.NginxHost != "host" { + t.Errorf("expected NginxHost to be 'host', got %s", eventWithIDAndHost.NginxHost) } - if eventWithIdAndHost.ClientType != clientType { - t.Errorf("expected ClientType to be '%s', got %s", clientType, eventWithIdAndHost.ClientType) + if eventWithIDAndHost.ClientType != clientType { + t.Errorf("expected ClientType to be '%s', got %s", clientType, eventWithIDAndHost.ClientType) } } func TestTypeNameCreated(t *testing.T) { + t.Parallel() event := NewServerUpdateEvent(Created, "upstream", clientType, emptyUpstreamServers) if event.TypeName() != "Created" { @@ -48,6 +50,7 @@ func TestTypeNameCreated(t *testing.T) { } func TestTypeNameUpdated(t *testing.T) { + t.Parallel() event := NewServerUpdateEvent(Updated, "upstream", clientType, emptyUpstreamServers) if event.TypeName() != "Updated" { @@ -56,6 +59,7 @@ func TestTypeNameUpdated(t *testing.T) { } func TestTypeNameDeleted(t *testing.T) { + t.Parallel() event := NewServerUpdateEvent(Deleted, "upstream", clientType, emptyUpstreamServers) if event.TypeName() != "Deleted" { @@ -64,6 +68,7 @@ func TestTypeNameDeleted(t *testing.T) { } func TestTypeNameUnknown(t *testing.T) { + t.Parallel() event := NewServerUpdateEvent(EventType(100), "upstream", clientType, emptyUpstreamServers) if event.TypeName() != "Unknown" { diff --git a/internal/core/upstream_server.go b/internal/core/upstream_server.go index 7c89b1e..eeb72ac 100644 --- a/internal/core/upstream_server.go +++ b/internal/core/upstream_server.go @@ -5,7 +5,8 @@ package core -// UpstreamServer represents a single upstream server. This is an internal representation used to abstract the definition +// UpstreamServer represents a single upstream server. +// This is an internal representation used to abstract the definition // of an upstream server from any specific client. type UpstreamServer struct { diff --git a/internal/core/upstream_server_test.go b/internal/core/upstream_server_test.go index 7b0eed5..91592cd 100644 --- a/internal/core/upstream_server_test.go +++ b/internal/core/upstream_server_test.go @@ -8,6 +8,7 @@ package core import "testing" func TestNewUpstreamServer(t *testing.T) { + t.Parallel() host := "localhost" us := NewUpstreamServer(host) if us.Host != host { diff --git a/internal/observation/handler.go b/internal/observation/handler.go index 8e89745..5584939 100644 --- a/internal/observation/handler.go +++ b/internal/observation/handler.go @@ -47,7 +47,11 @@ type Handler struct { } // NewHandler creates a new event handler -func NewHandler(settings *configuration.Settings, synchronizer synchronization.Interface, eventQueue workqueue.RateLimitingInterface) *Handler { +func NewHandler( + settings *configuration.Settings, + synchronizer synchronization.Interface, + eventQueue workqueue.RateLimitingInterface, +) *Handler { return &Handler{ eventQueue: eventQueue, settings: settings, diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go index e5bef3d..b7a4791 100644 --- a/internal/observation/handler_test.go +++ b/internal/observation/handler_test.go @@ -18,6 +18,7 @@ import ( ) func TestHandler_AddsEventToSynchronizer(t *testing.T) { + t.Parallel() _, _, synchronizer, handler, err := buildHandler() if err != nil { t.Errorf(`should have been no error, %v`, err) @@ -45,7 +46,11 @@ func TestHandler_AddsEventToSynchronizer(t *testing.T) { } } -func buildHandler() (*configuration.Settings, workqueue.RateLimitingInterface, *mocks.MockSynchronizer, *Handler, error) { +func buildHandler() ( + *configuration.Settings, + workqueue.RateLimitingInterface, + *mocks.MockSynchronizer, *Handler, error, +) { settings, err := configuration.NewSettings(context.Background(), nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf(`should have been no error, %v`, err) diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 74e2d05..a022f2e 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -51,10 +51,7 @@ func (w *Watcher) Initialize() error { logrus.Debug("Watcher::Initialize") var err error - w.informer, err = w.buildInformer() - if err != nil { - return fmt.Errorf(`initialization error: %w`, err) - } + w.informer = w.buildInformer() err = w.initializeEventListeners() if err != nil { @@ -78,7 +75,11 @@ func (w *Watcher) Watch() error { go w.informer.Run(w.settings.Context.Done()) - if !cache.WaitForNamedCacheSync(w.settings.Handler.WorkQueueSettings.Name, w.settings.Context.Done(), w.informer.HasSynced) { + if !cache.WaitForNamedCacheSync( + w.settings.Handler.WorkQueueSettings.Name, + w.settings.Context.Done(), + w.informer.HasSynced, + ) { return fmt.Errorf(`error occurred waiting for the cache to sync`) } @@ -86,7 +87,8 @@ func (w *Watcher) Watch() error { return nil } -// buildEventHandlerForAdd creates a function that is used as an event handler for the informer when Add events are raised. +// buildEventHandlerForAdd creates a function that is used as an event handler +// for the informer when Add events are raised. func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { logrus.Info("Watcher::buildEventHandlerForAdd") return func(obj interface{}) { @@ -102,7 +104,8 @@ func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { } } -// buildEventHandlerForDelete creates a function that is used as an event handler for the informer when Delete events are raised. +// buildEventHandlerForDelete creates a function that is used as an event handler +// for the informer when Delete events are raised. func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { logrus.Info("Watcher::buildEventHandlerForDelete") return func(obj interface{}) { @@ -118,7 +121,8 @@ func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { } } -// buildEventHandlerForUpdate creates a function that is used as an event handler for the informer when Update events are raised. +// buildEventHandlerForUpdate creates a function that is used as an event handler +// for the informer when Update events are raised. func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { logrus.Info("Watcher::buildEventHandlerForUpdate") return func(previous, updated interface{}) { @@ -135,14 +139,16 @@ func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { } // buildInformer creates the informer used to watch for changes to Kubernetes resources. -func (w *Watcher) buildInformer() (cache.SharedIndexInformer, error) { +func (w *Watcher) buildInformer() cache.SharedIndexInformer { logrus.Debug("Watcher::buildInformer") options := informers.WithNamespace(w.settings.Watcher.NginxIngressNamespace) - factory := informers.NewSharedInformerFactoryWithOptions(w.settings.K8sClient, w.settings.Watcher.ResyncPeriod, options) + factory := informers.NewSharedInformerFactoryWithOptions( + w.settings.K8sClient, w.settings.Watcher.ResyncPeriod, options, + ) informer := factory.Core().V1().Services().Informer() - return informer, nil + return informer } // initializeEventListeners initializes the event listeners for the informer. diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index 7c820bf..2a6d94b 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -15,6 +15,7 @@ import ( ) func TestWatcher_MustInitialize(t *testing.T) { + t.Parallel() watcher, _ := buildWatcher() if err := watcher.Watch(); err == nil { t.Errorf("Expected error, got %s", err) diff --git a/internal/probation/check_test.go b/internal/probation/check_test.go index 208c9a4..95358e5 100644 --- a/internal/probation/check_test.go +++ b/internal/probation/check_test.go @@ -8,6 +8,7 @@ package probation import "testing" func TestCheck_LiveCheck(t *testing.T) { + t.Parallel() check := LiveCheck{} if !check.Check() { t.Errorf("LiveCheck should return true") @@ -15,6 +16,7 @@ func TestCheck_LiveCheck(t *testing.T) { } func TestCheck_ReadyCheck(t *testing.T) { + t.Parallel() check := ReadyCheck{} if !check.Check() { t.Errorf("ReadyCheck should return true") @@ -22,6 +24,7 @@ func TestCheck_ReadyCheck(t *testing.T) { } func TestCheck_StartupCheck(t *testing.T) { + t.Parallel() check := StartupCheck{} if !check.Check() { t.Errorf("StartupCheck should return true") diff --git a/internal/probation/server.go b/internal/probation/server.go index ff23e33..84b5b67 100644 --- a/internal/probation/server.go +++ b/internal/probation/server.go @@ -8,6 +8,7 @@ package probation import ( "fmt" "net/http" + "time" "github.com/sirupsen/logrus" ) @@ -59,7 +60,7 @@ func (hs *HealthServer) Start() { mux.HandleFunc("/livez", hs.HandleLive) mux.HandleFunc("/readyz", hs.HandleReady) mux.HandleFunc("/startupz", hs.HandleStartup) - hs.httpServer = &http.Server{Addr: address, Handler: mux} + hs.httpServer = &http.Server{Addr: address, Handler: mux, ReadTimeout: 2 * time.Second} go func() { if err := hs.httpServer.ListenAndServe(); err != nil { diff --git a/internal/probation/server_test.go b/internal/probation/server_test.go index 4ea51e7..9c7d37d 100644 --- a/internal/probation/server_test.go +++ b/internal/probation/server_test.go @@ -14,6 +14,7 @@ import ( ) func TestHealthServer_HandleLive(t *testing.T) { + t.Parallel() server := NewHealthServer() writer := mocks.NewMockResponseWriter() server.HandleLive(writer, nil) @@ -24,6 +25,7 @@ func TestHealthServer_HandleLive(t *testing.T) { } func TestHealthServer_HandleReady(t *testing.T) { + t.Parallel() server := NewHealthServer() writer := mocks.NewMockResponseWriter() server.HandleReady(writer, nil) @@ -34,6 +36,7 @@ func TestHealthServer_HandleReady(t *testing.T) { } func TestHealthServer_HandleStartup(t *testing.T) { + t.Parallel() server := NewHealthServer() writer := mocks.NewMockResponseWriter() server.HandleStartup(writer, nil) @@ -44,6 +47,7 @@ func TestHealthServer_HandleStartup(t *testing.T) { } func TestHealthServer_HandleFailCheck(t *testing.T) { + t.Parallel() failCheck := mocks.NewMockCheck(false) server := NewHealthServer() writer := mocks.NewMockResponseWriter() @@ -56,6 +60,7 @@ func TestHealthServer_HandleFailCheck(t *testing.T) { } func TestHealthServer_Start(t *testing.T) { + t.Parallel() server := NewHealthServer() server.Start() @@ -65,6 +70,7 @@ func TestHealthServer_Start(t *testing.T) { if err != nil { t.Error(err) } + defer response.Body.Close() if response.StatusCode != http.StatusOK { t.Errorf("Expected status code %v, got %v", http.StatusAccepted, response.StatusCode) diff --git a/internal/synchronization/rand.go b/internal/synchronization/rand.go index 425b99a..6bf58d1 100644 --- a/internal/synchronization/rand.go +++ b/internal/synchronization/rand.go @@ -6,6 +6,7 @@ package synchronization import ( + // Try using crpyto if needed. "math/rand" "time" ) @@ -24,14 +25,14 @@ func RandomString(n int) string { b := make([]byte, n) for i := range b { // randomly select 1 character from given charset - b[i] = alphaNumeric[rand.Intn(len(alphaNumeric))] + b[i] = alphaNumeric[rand.Intn(len(alphaNumeric))] //nolint:gosec } return string(b) } // RandomMilliseconds returns a random duration between min and max milliseconds func RandomMilliseconds(min, max int) time.Duration { - randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) + randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec random := randomizer.Intn(max-min) + min return time.Millisecond * time.Duration(random) diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 5fc07f5..7522f3a 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -35,15 +35,19 @@ type Interface interface { } // Synchronizer is responsible for synchronizing the state of the Border Servers. -// Operating against the "nlk-synchronizer", it handles events by creating a Border Client as specified in the -// Service annotation for the Upstream. see application/border_client.go and application/application_constants.go for details. +// Operating against the "nlk-synchronizer", it handles events by creating +// a Border Client as specified in the Service annotation for the Upstream. +// See application/border_client.go and application/application_constants.go for details. type Synchronizer struct { eventQueue workqueue.RateLimitingInterface settings *configuration.Settings } // NewSynchronizer creates a new Synchronizer. -func NewSynchronizer(settings *configuration.Settings, eventQueue workqueue.RateLimitingInterface) (*Synchronizer, error) { +func NewSynchronizer( + settings *configuration.Settings, + eventQueue workqueue.RateLimitingInterface, +) (*Synchronizer, error) { synchronizer := Synchronizer{ eventQueue: eventQueue, settings: settings, @@ -79,7 +83,10 @@ func (s *Synchronizer) AddEvent(event *core.ServerUpdateEvent) { return } - after := RandomMilliseconds(s.settings.Synchronizer.MinMillisecondsJitter, s.settings.Synchronizer.MaxMillisecondsJitter) + after := RandomMilliseconds( + s.settings.Synchronizer.MinMillisecondsJitter, + s.settings.Synchronizer.MaxMillisecondsJitter, + ) s.eventQueue.AddAfter(event, after) } @@ -108,7 +115,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica var err error - httpClient, err := communication.NewHttpClient(s.settings) + httpClient, err := communication.NewHTTPClient(s.settings) if err != nil { return nil, fmt.Errorf(`error creating HTTP client: %v`, err) } @@ -130,7 +137,7 @@ func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.Se for hidx, host := range s.settings.NginxPlusHosts { for eidx, event := range event { id := fmt.Sprintf(`[%d:%d]-[%s]-[%s]-[%s]`, hidx, eidx, RandomString(12), event.UpstreamName, host) - updatedEvent := core.ServerUpdateEventWithIdAndHost(event, id, host) + updatedEvent := core.ServerUpdateEventWithIDAndHost(event, id, host) events = append(events, updatedEvent) } @@ -141,7 +148,7 @@ func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.Se // handleEvent dispatches an event to the proper handler function. func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { - logrus.Debugf(`Synchronizer::handleEvent: Id: %s`, event.Id) + logrus.Debugf(`Synchronizer::handleEvent: Id: %s`, event.ID) var err error @@ -160,7 +167,9 @@ func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { } if err == nil { - logrus.Infof(`Synchronizer::handleEvent: successfully %s the nginx+ host(s) for Upstream: %s: Id(%s)`, event.TypeName(), event.UpstreamName, event.Id) + logrus.Infof( + `Synchronizer::handleEvent: successfully %s the nginx+ host(s) for Upstream: %s: Id(%s)`, + event.TypeName(), event.UpstreamName, event.ID) } return err @@ -168,7 +177,7 @@ func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { // handleCreatedUpdatedEvent handles events of type Created or Updated. func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerUpdateEvent) error { - logrus.Debugf(`Synchronizer::handleCreatedUpdatedEvent: Id: %s`, serverUpdateEvent.Id) + logrus.Debugf(`Synchronizer::handleCreatedUpdatedEvent: Id: %s`, serverUpdateEvent.ID) var err error @@ -186,7 +195,7 @@ func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerU // handleDeletedEvent handles events of type Deleted. func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEvent) error { - logrus.Debugf(`Synchronizer::handleDeletedEvent: Id: %s`, serverUpdateEvent.Id) + logrus.Debugf(`Synchronizer::handleDeletedEvent: Id: %s`, serverUpdateEvent.ID) var err error @@ -233,7 +242,7 @@ func (s *Synchronizer) withRetry(err error, event *core.ServerUpdateEvent) { // TODO: Add Telemetry if s.eventQueue.NumRequeues(event) < s.settings.Synchronizer.RetryCount { // TODO: Make this configurable s.eventQueue.AddRateLimited(event) - logrus.Infof(`Synchronizer::withRetry: requeued event: %s; error: %v`, event.Id, err) + logrus.Infof(`Synchronizer::withRetry: requeued event: %s; error: %v`, event.ID, err) } else { s.eventQueue.Forget(event) logrus.Warnf(`Synchronizer::withRetry: event %#v has been dropped due to too many retries`, event) diff --git a/internal/synchronization/synchronizer_test.go b/internal/synchronization/synchronizer_test.go index ef510b8..3634513 100644 --- a/internal/synchronization/synchronizer_test.go +++ b/internal/synchronization/synchronizer_test.go @@ -16,7 +16,11 @@ import ( ) func TestSynchronizer_NewSynchronizer(t *testing.T) { + t.Parallel() settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } rateLimiter := &mocks.MockRateLimiter{} @@ -31,15 +35,19 @@ func TestSynchronizer_NewSynchronizer(t *testing.T) { } func TestSynchronizer_AddEventNoHosts(t *testing.T) { + t.Parallel() const expectedEventCount = 0 event := &core.ServerUpdateEvent{ - Id: "", + ID: "", NginxHost: "", Type: 0, UpstreamName: "", UpstreamServers: nil, } settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } rateLimiter := &mocks.MockRateLimiter{} synchronizer, err := NewSynchronizer(settings, rateLimiter) @@ -61,9 +69,13 @@ func TestSynchronizer_AddEventNoHosts(t *testing.T) { } func TestSynchronizer_AddEventOneHost(t *testing.T) { + t.Parallel() const expectedEventCount = 1 events := buildEvents(1) settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } settings.NginxPlusHosts = []string{"https://localhost:8080"} rateLimiter := &mocks.MockRateLimiter{} @@ -84,9 +96,13 @@ func TestSynchronizer_AddEventOneHost(t *testing.T) { } func TestSynchronizer_AddEventManyHosts(t *testing.T) { + t.Parallel() const expectedEventCount = 1 events := buildEvents(1) settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } settings.NginxPlusHosts = []string{ "https://localhost:8080", "https://localhost:8081", @@ -111,9 +127,13 @@ func TestSynchronizer_AddEventManyHosts(t *testing.T) { } func TestSynchronizer_AddEventsNoHosts(t *testing.T) { + t.Parallel() const expectedEventCount = 0 events := buildEvents(4) settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } rateLimiter := &mocks.MockRateLimiter{} synchronizer, err := NewSynchronizer(settings, rateLimiter) @@ -135,9 +155,13 @@ func TestSynchronizer_AddEventsNoHosts(t *testing.T) { } func TestSynchronizer_AddEventsOneHost(t *testing.T) { + t.Parallel() const expectedEventCount = 4 events := buildEvents(4) settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } settings.NginxPlusHosts = []string{"https://localhost:8080"} rateLimiter := &mocks.MockRateLimiter{} @@ -158,10 +182,14 @@ func TestSynchronizer_AddEventsOneHost(t *testing.T) { } func TestSynchronizer_AddEventsManyHosts(t *testing.T) { + t.Parallel() const eventCount = 4 events := buildEvents(eventCount) rateLimiter := &mocks.MockRateLimiter{} settings, err := configuration.NewSettings(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } settings.NginxPlusHosts = []string{ "https://localhost:8080", "https://localhost:8081", @@ -189,7 +217,7 @@ func buildEvents(count int) core.ServerUpdateEvents { events := make(core.ServerUpdateEvents, count) for i := 0; i < count; i++ { events[i] = &core.ServerUpdateEvent{ - Id: fmt.Sprintf("id-%v", i), + ID: fmt.Sprintf("id-%v", i), NginxHost: "https://localhost:8080", Type: 0, UpstreamName: "", diff --git a/internal/translation/translator.go b/internal/translation/translator.go index 98ff368..5dc49ec 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -40,16 +40,18 @@ func filterPorts(ports []v1.ServicePort) []v1.ServicePort { } // buildServerUpdateEvents builds a list of ServerUpdateEvents based on the event type -// The NGINX+ Client uses a list of servers for Created and Updated events; the client performs reconciliation between -// the list of servers in the NGINX+ Client call and the list of servers in NGINX+. -// The NGINX+ Client uses a single server for Deleted events; so the list of servers is broken up into individual events. +// The NGINX+ Client uses a list of servers for Created and Updated events. +// The client performs reconciliation between the list of servers in the NGINX+ Client call +// and the list of servers in NGINX+. +// The NGINX+ Client uses a single server for Deleted events; +// so the list of servers is broken up into individual events. func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.ServerUpdateEvents, error) { logrus.Debugf("Translate::buildServerUpdateEvents(ports=%#v)", ports) events := core.ServerUpdateEvents{} for _, port := range ports { ingressName := fixIngressName(port.Name) - upstreamServers, _ := buildUpstreamServers(event.NodeIps, port) + upstreamServers := buildUpstreamServers(event.NodeIps, port) clientType := getClientType(port.Name, event.Service.Annotations) switch event.Type { @@ -61,7 +63,9 @@ func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.Se case core.Deleted: for _, server := range upstreamServers { - events = append(events, core.NewServerUpdateEvent(event.Type, ingressName, clientType, core.UpstreamServers{server})) + events = append(events, core.NewServerUpdateEvent( + event.Type, ingressName, clientType, core.UpstreamServers{server}, + )) } default: @@ -73,16 +77,16 @@ func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.Se return events, nil } -func buildUpstreamServers(nodeIps []string, port v1.ServicePort) (core.UpstreamServers, error) { +func buildUpstreamServers(nodeIPs []string, port v1.ServicePort) core.UpstreamServers { var servers core.UpstreamServers - for _, nodeIp := range nodeIps { - host := fmt.Sprintf("%s:%d", nodeIp, port.NodePort) + for _, nodeIP := range nodeIPs { + host := fmt.Sprintf("%s:%d", nodeIP, port.NodePort) server := core.NewUpstreamServer(host) servers = append(servers, server) } - return servers, nil + return servers } // fixIngressName removes the NlkPrefix from the port name @@ -100,5 +104,5 @@ func getClientType(portName string, annotations map[string]string) string { } } - return application.ClientTypeNginxHttp + return application.ClientTypeNginxHTTP } diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index a393f64..b53abcc 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -29,6 +29,7 @@ const ( */ func TestCreatedTranslateNoPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 0 service := defaultService() @@ -46,6 +47,7 @@ func TestCreatedTranslateNoPorts(t *testing.T) { } func TestCreatedTranslateNoInterestingPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 0 const portCount = 1 @@ -65,6 +67,7 @@ func TestCreatedTranslateNoInterestingPorts(t *testing.T) { } func TestCreatedTranslateOneInterestingPort(t *testing.T) { + t.Parallel() const expectedEventCount = 1 const portCount = 1 @@ -86,6 +89,7 @@ func TestCreatedTranslateOneInterestingPort(t *testing.T) { } func TestCreatedTranslateManyInterestingPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 4 const portCount = 4 @@ -107,6 +111,7 @@ func TestCreatedTranslateManyInterestingPorts(t *testing.T) { } func TestCreatedTranslateManyMixedPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 2 const portCount = 6 const updatablePortCount = 2 @@ -129,6 +134,7 @@ func TestCreatedTranslateManyMixedPorts(t *testing.T) { } func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 2 const portCount = 6 const updatablePortCount = 2 @@ -155,6 +161,7 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { */ func TestUpdatedTranslateNoPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 0 service := defaultService() @@ -172,6 +179,7 @@ func TestUpdatedTranslateNoPorts(t *testing.T) { } func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 0 const portCount = 1 @@ -191,6 +199,7 @@ func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { } func TestUpdatedTranslateOneInterestingPort(t *testing.T) { + t.Parallel() const expectedEventCount = 1 const portCount = 1 @@ -212,6 +221,7 @@ func TestUpdatedTranslateOneInterestingPort(t *testing.T) { } func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 4 const portCount = 4 @@ -233,6 +243,7 @@ func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { } func TestUpdatedTranslateManyMixedPorts(t *testing.T) { + t.Parallel() const expectedEventCount = 2 const portCount = 6 const updatablePortCount = 2 @@ -255,6 +266,7 @@ func TestUpdatedTranslateManyMixedPorts(t *testing.T) { } func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 2 const portCount = 6 const updatablePortCount = 2 @@ -281,6 +293,7 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { */ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 0 service := defaultService() @@ -300,6 +313,7 @@ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { } func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 0 const portCount = 1 @@ -321,6 +335,8 @@ func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { } func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { + t.Parallel() + const expectedEventCount = 0 const portCount = 1 @@ -342,6 +358,7 @@ func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { } func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 0 const portCount = 4 @@ -363,6 +380,7 @@ func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { } func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 0 const portCount = 6 const updatablePortCount = 2 @@ -385,6 +403,7 @@ func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { } func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { + t.Parallel() const expectedEventCount = 0 service := defaultService() @@ -404,6 +423,7 @@ func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { } func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { + t.Parallel() const expectedEventCount = 0 const portCount = 1 @@ -425,6 +445,7 @@ func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { } func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { + t.Parallel() const expectedEventCount = 1 const portCount = 1 @@ -446,6 +467,7 @@ func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { } func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { + t.Parallel() const expectedEventCount = 4 const portCount = 4 @@ -467,6 +489,7 @@ func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { } func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { + t.Parallel() const expectedEventCount = 2 const portCount = 6 const updatablePortCount = 2 @@ -489,6 +512,7 @@ func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { } func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { + t.Parallel() const expectedEventCount = 0 service := defaultService() @@ -508,6 +532,7 @@ func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { } func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { + t.Parallel() const portCount = 1 const updatablePortCount = 0 const expectedEventCount = updatablePortCount * ManyNodes @@ -530,6 +555,7 @@ func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { } func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { + t.Parallel() const portCount = 1 const expectedEventCount = portCount * ManyNodes @@ -551,6 +577,7 @@ func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { } func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { + t.Parallel() const portCount = 4 const expectedEventCount = portCount * ManyNodes @@ -572,6 +599,7 @@ func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { } func TestDeletedTranslateManyMixedPortsAndManyNodes(t *testing.T) { + t.Parallel() const portCount = 6 const updatablePortCount = 2 const expectedEventCount = updatablePortCount * ManyNodes @@ -650,7 +678,7 @@ func generatePorts(portCount int) []v1.ServicePort { // This is probably A Little Bit of Too Muchâ„¢, but helps to ensure ordering is not a factor. func generateUpdatablePorts(portCount int, updatableCount int) []v1.ServicePort { - var ports []v1.ServicePort + ports := []v1.ServicePort{} updatable := make([]string, updatableCount) nonupdatable := make([]string, portCount-updatableCount) @@ -663,7 +691,9 @@ func generateUpdatablePorts(portCount int, updatableCount int) []v1.ServicePort nonupdatable[j] = "olm-" } - prefixes := append(updatable, nonupdatable...) + var prefixes []string + prefixes = append(prefixes, updatable...) + prefixes = append(prefixes, nonupdatable...) source := rand.NewSource(time.Now().UnixNano()) random := rand.New(source) diff --git a/test/mocks/mock_nginx_plus_client.go b/test/mocks/mock_nginx_plus_client.go index 2c16e12..00b560e 100644 --- a/test/mocks/mock_nginx_plus_client.go +++ b/test/mocks/mock_nginx_plus_client.go @@ -36,7 +36,10 @@ func (m MockNginxClient) DeleteStreamServer(_ string, _ string) error { return nil } -func (m MockNginxClient) UpdateStreamServers(_ string, _ []nginxClient.StreamUpstreamServer) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) { +func (m MockNginxClient) UpdateStreamServers( + _ string, + _ []nginxClient.StreamUpstreamServer, +) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) { m.CalledFunctions["UpdateStreamServers"] = true if m.Error != nil { @@ -56,7 +59,10 @@ func (m MockNginxClient) DeleteHTTPServer(_ string, _ string) error { return nil } -func (m MockNginxClient) UpdateHTTPServers(_ string, _ []nginxClient.UpstreamServer) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) { +func (m MockNginxClient) UpdateHTTPServers( + _ string, + _ []nginxClient.UpstreamServer, +) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) { m.CalledFunctions["UpdateHTTPServers"] = true if m.Error != nil { From ef78913f7ce22974ab6328d6723e4c02fa86fe7e Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 29 Mar 2024 18:26:15 -0700 Subject: [PATCH 005/136] Update go version and deps --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 38f6adb..7a95a39 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.19 +go 1.21 require ( github.com/nginxinc/nginx-plus-go-client v0.10.0 diff --git a/go.sum b/go.sum index 867f71f..7d3d0e5 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -160,7 +161,9 @@ github.com/nginxinc/nginx-plus-go-client v0.10.0/go.mod h1:0v3RsQCvRn/IyrMtW+DK6 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -179,6 +182,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 2f41f5e1a401feea0b071cbe4a0cba345ca74a29 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 8 Jul 2024 15:47:57 -0700 Subject: [PATCH 006/136] Disable exhaustruct linter for now --- .golangci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index d89f3f5..6cb3587 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,6 @@ linters: # Supported linters: https://golangci-lint.run/usage/linters/ enable: - errcheck - - exhaustruct - gosimple - govet - ineffassign From 196b8457381756bf5d64c0da91430c77c3a7044a Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 26 Jun 2024 13:39:35 -0600 Subject: [PATCH 007/136] NLB-4655 NLK will retry a work item to update upstreams indefinitely The primary intent behind this is to keep retrying updates which may be made before the controlplane has registered the existence of a named upstream in the customer's NGINX configuration. --- internal/synchronization/synchronizer.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 7522f3a..9d1a697 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -20,7 +20,6 @@ import ( // Interface defines the interface needed to implement a synchronizer. type Interface interface { - // AddEvents adds a list of events to the queue. AddEvents(events core.ServerUpdateEvents) @@ -240,13 +239,8 @@ func (s *Synchronizer) withRetry(err error, event *core.ServerUpdateEvent) { logrus.Debug("Synchronizer::withRetry") if err != nil { // TODO: Add Telemetry - if s.eventQueue.NumRequeues(event) < s.settings.Synchronizer.RetryCount { // TODO: Make this configurable - s.eventQueue.AddRateLimited(event) - logrus.Infof(`Synchronizer::withRetry: requeued event: %s; error: %v`, event.ID, err) - } else { - s.eventQueue.Forget(event) - logrus.Warnf(`Synchronizer::withRetry: event %#v has been dropped due to too many retries`, event) - } + s.eventQueue.AddRateLimited(event) + logrus.Infof(`Synchronizer::withRetry: requeued event: %s; error: %v`, event.ID, err) } else { s.eventQueue.Forget(event) } // TODO: Add error logging From 46ec67bc003672a86fc278748402fbabcb6eb5b4 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 23 Jul 2024 08:48:55 -0700 Subject: [PATCH 008/136] Update binary/docker img to nginxaas-operator --- Dockerfile | 6 +++--- scripts/build.sh | 2 +- scripts/docker.sh | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0a12779..d80281c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,6 @@ COPY docker-user /etc/passwd USER 101 COPY --from=base-certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -FROM base as nlk -ENTRYPOINT ["/nlk"] -COPY build/nlk / +FROM base as nginxaas-operator +ENTRYPOINT ["/nginxaas-operator"] +COPY build/nginxaas-operator / diff --git a/scripts/build.sh b/scripts/build.sh index aba9ad5..b5be0ed 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -36,5 +36,5 @@ ldflags=( go build \ -v -tags "release osusergo" \ -ldflags "${ldflags[*]}" \ - -o "${BUILD_DIR}/nlk" \ + -o "${BUILD_DIR}/nginxaas-operator" \ "$pkg_path" diff --git a/scripts/docker.sh b/scripts/docker.sh index 0015f49..4193f55 100755 --- a/scripts/docker.sh +++ b/scripts/docker.sh @@ -69,7 +69,7 @@ parse_args() { } # MAIN -image="nlk" +image="nginxaas-operator" parse_args "$@" init_ci_vars From ac9100a695713b7eaa5dc5564511766310da788d Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 31 Jul 2024 13:57:22 -0700 Subject: [PATCH 009/136] NLB-5341: Disable dep caching Disabling caching temporarily to figure out the issue and we can re-enable it after that. --- .gitlab-ci.yml | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 08dfa9a..da34f53 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,22 +30,23 @@ workflow: .import-devops-core-services: &import-devops-core-services | source ${CI_PROJECT_DIR}/.devops.sh -.go-cache: - variables: - GOPATH: $CI_PROJECT_DIR/.go - cache: - key: - files: - - go.mod - - go.sum - paths: - - .go/pkg/mod/ +# Disabld due to: NLB-5341 +# .go-cache: +# variables: +# GOPATH: $CI_PROJECT_DIR/.go +# cache: +# key: +# files: +# - go.mod +# - go.sum +# paths: +# - .go/pkg/mod/ -.go-cache-readonly: - extends: - - .go-cache - cache: - policy: pull +# .go-cache-readonly: +# extends: +# - .go-cache +# cache: +# policy: pull .golang-private: &golang-private - | @@ -62,7 +63,7 @@ lint + unit-test + build: image: $DEVTOOLS_IMG extends: - .devops-docker-cicd-large - - .go-cache + # - .go-cache script: - *golang-private - | @@ -93,7 +94,7 @@ unit-test-data-race: GO_DATA_RACE: "true" extends: - .default-runner-large - - .go-cache-readonly + # - .go-cache-readonly script: - *golang-private - time make test @@ -113,7 +114,7 @@ whitesource-scan: stage: lint+test+build extends: - .default-runner - - .go-cache + # - .go-cache - .whitesource-template-go variables: NESTED: "true" From 1f83f55e4d83461c89c90fdb225d64db817759b9 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 7 Aug 2024 13:44:57 -0700 Subject: [PATCH 010/136] Run the informer in go routine NOTE: This commit was accidentally missed out in the first iteration of this fork and is present in upstream: https://github.com/nginxinc/nginx-loadbalancer-kubernetes/blob/main/internal/configuration/settings.go#L189 The code needs to move forward to start the watchers and health server (side note: we should also think about the ordering of these at some point) and not running the infomer in a go routine prevents the program from further execution until the context is canceled. In the current iteration on main, the controller is stuck on the informer, and then k8s kills the service and restarts it since the health server is not up. --- internal/configuration/settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 05c3690..af7c2d8 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -189,7 +189,7 @@ func (s *Settings) Initialize() error { s.Certificates = certificates - certificates.Run() //nolint:errcheck + go certificates.Run() //nolint:errcheck logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieving nlk-config ConfigMap") configMap, err := s.K8sClient.CoreV1().ConfigMaps(ConfigMapsNamespace).Get( From 8512c6a1623184b3d4dbaed404685ed717c69de0 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Mon, 29 Jul 2024 16:22:01 -0600 Subject: [PATCH 011/136] NLB-4617 Watcher filters events by annotation on ingress service name The user specifies the ingress service whose events the application should watch through setting the "service-annotation-match" annotation on the application's config map. Only events with a matching service annotation will be passed by the watcher to the handlers. The informer now listens to events from all namespaces. This frees the end user from the restriction of only using the nginx ingress controller. --- internal/configuration/settings.go | 27 +++++++++++++++------- internal/observation/watcher.go | 37 ++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index af7c2d8..857bd43 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -40,6 +40,14 @@ const ( // The value of the annotation determines which BorderServer implementation will be used. // See the documentation in the `application/application_constants.go` file for details. PortAnnotationPrefix = "nginxinc.io" + + // ServiceAnnotationMatchKey is the key name of the annotation in the application's config map + // that identifies the ingress service whose events will be monitored. + ServiceAnnotationMatchKey = "service-annotation-match" + + // DefaultServiceAnnotation is the default name of the ingress service whose events will be + // monitored. + DefaultServiceAnnotation = "nginxaas" ) // WorkQueueSettings contains the configuration values needed by the Work Queues. @@ -62,7 +70,6 @@ type WorkQueueSettings struct { // HandlerSettings contains the configuration values needed by the Handler. type HandlerSettings struct { - // RetryCount is the number of times the Handler will attempt to process a message before giving up. RetryCount int @@ -75,9 +82,8 @@ type HandlerSettings struct { // WatcherSettings contains the configuration values needed by the Watcher. type WatcherSettings struct { - - // NginxIngressNamespace is the namespace used to filter Services in the Watcher. - NginxIngressNamespace string + // ServiceAnnotation is the annotation of the ingress service whose events the watcher should monitor. + ServiceAnnotation string // ResyncPeriod is the value used to set the resync period for the underlying SharedInformer. ResyncPeriod time.Duration @@ -85,7 +91,6 @@ type WatcherSettings struct { // SynchronizerSettings contains the configuration values needed by the Synchronizer. type SynchronizerSettings struct { - // MaxMillisecondsJitter is the maximum number of milliseconds that will be applied when adding an event to the queue. MaxMillisecondsJitter int @@ -104,7 +109,6 @@ type SynchronizerSettings struct { // Settings contains the configuration values needed by the application. type Settings struct { - // Context is the context used to control the application. Context context.Context @@ -165,8 +169,8 @@ func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings }, }, Watcher: WatcherSettings{ - NginxIngressNamespace: "nginx-ingress", - ResyncPeriod: 0, + ResyncPeriod: 0, + ServiceAnnotation: DefaultServiceAnnotation, }, } @@ -313,6 +317,13 @@ func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { logrus.Warnf("Settings::handleUpdateEvent: client-certificate key not found in ConfigMap") } + if serviceAnnotation, found := configMap.Data[ServiceAnnotationMatchKey]; found { + s.Watcher.ServiceAnnotation = serviceAnnotation + } else { + s.Watcher.ServiceAnnotation = DefaultServiceAnnotation + } + logrus.Debugf("Settings::handleUpdateEvent: %s: %s", ServiceAnnotationMatchKey, s.Watcher.ServiceAnnotation) + setLogLevel(configMap.Data["log-level"]) logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index a022f2e..a07ef12 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -24,7 +24,6 @@ import ( // Particularly, Services in the namespace defined in the WatcherSettings::NginxIngressNamespace setting. // When a change is detected, an Event is generated and added to the Handler's queue. type Watcher struct { - // eventHandlerRegistration is used to track the event handlers eventHandlerRegistration interface{} @@ -87,17 +86,32 @@ func (w *Watcher) Watch() error { return nil } +// isDesiredService returns whether the user has configured the given service for watching. +func (w *Watcher) isDesiredService(service *v1.Service) bool { + annotation, ok := service.Annotations["nginx.com/nginxaas"] + if !ok { + return false + } + + return annotation == w.settings.Watcher.ServiceAnnotation +} + // buildEventHandlerForAdd creates a function that is used as an event handler // for the informer when Add events are raised. func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { logrus.Info("Watcher::buildEventHandlerForAdd") return func(obj interface{}) { + service := obj.(*v1.Service) + if !w.isDesiredService(service) { + return + } + nodeIps, err := w.retrieveNodeIps() if err != nil { logrus.Errorf(`error occurred retrieving node ips: %v`, err) return } - service := obj.(*v1.Service) + var previousService *v1.Service e := core.NewEvent(core.Created, service, previousService, nodeIps) w.handler.AddRateLimitedEvent(&e) @@ -109,12 +123,17 @@ func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { logrus.Info("Watcher::buildEventHandlerForDelete") return func(obj interface{}) { + service := obj.(*v1.Service) + if !w.isDesiredService(service) { + return + } + nodeIps, err := w.retrieveNodeIps() if err != nil { logrus.Errorf(`error occurred retrieving node ips: %v`, err) return } - service := obj.(*v1.Service) + var previousService *v1.Service e := core.NewEvent(core.Deleted, service, previousService, nodeIps) w.handler.AddRateLimitedEvent(&e) @@ -126,12 +145,18 @@ func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { logrus.Info("Watcher::buildEventHandlerForUpdate") return func(previous, updated interface{}) { + // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API + service := updated.(*v1.Service) + if !w.isDesiredService(service) { + return + } + nodeIps, err := w.retrieveNodeIps() if err != nil { logrus.Errorf(`error occurred retrieving node ips: %v`, err) return } - service := updated.(*v1.Service) + previousService := previous.(*v1.Service) e := core.NewEvent(core.Updated, service, previousService, nodeIps) w.handler.AddRateLimitedEvent(&e) @@ -142,9 +167,8 @@ func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { func (w *Watcher) buildInformer() cache.SharedIndexInformer { logrus.Debug("Watcher::buildInformer") - options := informers.WithNamespace(w.settings.Watcher.NginxIngressNamespace) factory := informers.NewSharedInformerFactoryWithOptions( - w.settings.K8sClient, w.settings.Watcher.ResyncPeriod, options, + w.settings.K8sClient, w.settings.Watcher.ResyncPeriod, ) informer := factory.Core().V1().Services().Informer() @@ -185,7 +209,6 @@ func (w *Watcher) retrieveNodeIps() ([]string, error) { } for _, node := range nodes.Items { - // this is kind of a broad assumption, should probably make this a configurable option if w.notMasterNode(node) { for _, address := range node.Status.Addresses { From 7446d640869bca61421b547bb7317f04a90720a9 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 26 Jul 2024 09:25:37 -0600 Subject: [PATCH 012/136] NLB-4823 Translator assumes that port names provide context and upstream name The port name should now be formatted like this: "http-tea", where the first part of the string is the context type (either "http" or "stream") and the second part of the string after the hyphen is the name of the upstream. --- internal/observation/handler_test.go | 2 +- internal/translation/translator.go | 55 +++++++++---------------- internal/translation/translator_test.go | 7 ++-- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go index b7a4791..72b6d8f 100644 --- a/internal/observation/handler_test.go +++ b/internal/observation/handler_test.go @@ -30,7 +30,7 @@ func TestHandler_AddsEventToSynchronizer(t *testing.T) { Spec: v1.ServiceSpec{ Ports: []v1.ServicePort{ { - Name: "nlk-back", + Name: "http-back", }, }, }, diff --git a/internal/translation/translator.go b/internal/translation/translator.go index 5dc49ec..9dad7f8 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -9,8 +9,6 @@ import ( "fmt" "strings" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" @@ -21,22 +19,7 @@ import ( func Translate(event *core.Event) (core.ServerUpdateEvents, error) { logrus.Debug("Translate::Translate") - portsOfInterest := filterPorts(event.Service.Spec.Ports) - - return buildServerUpdateEvents(portsOfInterest, event) -} - -// filterPorts returns a list of ports that have the NlkPrefix in the port name. -func filterPorts(ports []v1.ServicePort) []v1.ServicePort { - var portsOfInterest []v1.ServicePort - - for _, port := range ports { - if strings.HasPrefix(port.Name, configuration.NlkPrefix) { - portsOfInterest = append(portsOfInterest, port) - } - } - - return portsOfInterest + return buildServerUpdateEvents(event.Service.Spec.Ports, event) } // buildServerUpdateEvents builds a list of ServerUpdateEvents based on the event type @@ -50,21 +33,25 @@ func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.Se events := core.ServerUpdateEvents{} for _, port := range ports { - ingressName := fixIngressName(port.Name) + context, upstreamName, err := getContextAndUpstreamName(port) + if err != nil { + logrus.Info(err) + continue + } + upstreamServers := buildUpstreamServers(event.NodeIps, port) - clientType := getClientType(port.Name, event.Service.Annotations) switch event.Type { case core.Created: fallthrough case core.Updated: - events = append(events, core.NewServerUpdateEvent(event.Type, ingressName, clientType, upstreamServers)) + events = append(events, core.NewServerUpdateEvent(event.Type, upstreamName, context, upstreamServers)) case core.Deleted: for _, server := range upstreamServers { events = append(events, core.NewServerUpdateEvent( - event.Type, ingressName, clientType, core.UpstreamServers{server}, + event.Type, upstreamName, context, core.UpstreamServers{server}, )) } @@ -89,20 +76,18 @@ func buildUpstreamServers(nodeIPs []string, port v1.ServicePort) core.UpstreamSe return servers } -// fixIngressName removes the NlkPrefix from the port name -func fixIngressName(name string) string { - return name[4:] -} +// getContextAndUpstreamName returns the nginx context being supplied by the port (either "http" or "stream") +// and the upstream name. +func getContextAndUpstreamName(port v1.ServicePort) (clientType string, appName string, err error) { + parts := strings.Split(port.Name, "-") + if len(parts) != 2 { + return clientType, appName, + fmt.Errorf("ignoring port %s because it is not in the format [http|stream]-{upstreamName}", port.Name) + } -// getClientType returns the client type for the port, defaults to ClientTypeNginxHttp if no Annotation is found. -func getClientType(portName string, annotations map[string]string) string { - key := fmt.Sprintf("%s/%s", configuration.PortAnnotationPrefix, portName) - logrus.Infof("getClientType: key=%s", key) - if annotations != nil { - if clientType, ok := annotations[key]; ok { - return clientType - } + if parts[0] != "http" && parts[0] != "stream" { + return clientType, appName, fmt.Errorf("port name %s does not include \"http\" or \"stream\" context", port.Name) } - return application.ClientTypeNginxHTTP + return parts[0], parts[1], nil } diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index b53abcc..5b508c3 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" v1 "k8s.io/api/core/v1" ) @@ -682,9 +681,11 @@ func generateUpdatablePorts(portCount int, updatableCount int) []v1.ServicePort updatable := make([]string, updatableCount) nonupdatable := make([]string, portCount-updatableCount) + contexts := []string{"http-", "stream-"} for i := range updatable { - updatable[i] = configuration.NlkPrefix + randomIndex := int(rand.Float32() * 2.0) + updatable[i] = contexts[randomIndex] } for j := range nonupdatable { @@ -701,7 +702,7 @@ func generateUpdatablePorts(portCount int, updatableCount int) []v1.ServicePort for i, prefix := range prefixes { ports = append(ports, v1.ServicePort{ - Name: fmt.Sprintf("%sport-%d", prefix, i), + Name: fmt.Sprintf("%supstream%d", prefix, i), }) } From 6f3046040d8bb248871150457832ad2735a33e6f Mon Sep 17 00:00:00 2001 From: sarna Date: Sat, 17 Aug 2024 16:27:27 -0700 Subject: [PATCH 013/136] NLB-5282: Allow images to be pushed to Dockerhub We need to be able to publish the operator to dockerhub in order to be publicly available for customers. Following what we have in ARP as a release strategy where a release tag action would publish the image to dockerhub. --- .gitlab-ci.yml | 10 ++++++++++ scripts/release.sh | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100755 scripts/release.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da34f53..d59d1bb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -177,3 +177,13 @@ tag: - if: '$CI_PIPELINE_SOURCE == "schedule"' when: never - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + +dockerhub-push: + stage: release + image: $DEVTOOLS_IMG + extends: + - .devops-docker-cicd + script: + - ./scripts/release.sh + rules: + - if: '$CI_COMMIT_TAG =~ /^release-[\d]+\.[\d]+\.[\d]+/' diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..7c85792 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [[ "${CI}" != "true" ]]; then + echo "This script is meant to be run in the CI." + exit 1 +fi + +pttn="^release-[0-9]+\.[0-9]+\.[0-9]+" +if ! [[ "${CI_COMMIT_TAG}" =~ $pttn ]]; then + echo "CI_COMMIT_TAG needs to be set to valid semver format." + exit 1 +fi + +ROOT_DIR=$(git rev-parse --show-toplevel) +source ${ROOT_DIR}/.devops.sh + +DOCKERHUB_USERNAME=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".username") +if [[ -z "${DOCKERHUB_USERNAME}" ]]; then + echo "DOCKERHUB_USERNAME needs to be set." + exit 1 +fi + +DOCKERHUB_PASSWORD=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".password") +if [[ -z "${DOCKERHUB_PASSWORD}" ]]; then + echo "DOCKERHUB_PASSWORD needs to be set." + exit 1 +fi + +SRC_REGISTRY="${DEVOPS_DOCKER_URL}" +SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" +SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") +SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" + +DST_REGISTRY="docker.io" +DST_PATH="nginx/nginxaas-operator" +DST_TAG="${CI_COMMIT_TAG}" +DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" + +devops.docker.login +docker pull "${SRC_IMG}" +docker tag "${SRC_IMG}" "${DST_IMG}" + +# Login to Dockerhub and push release image to it. +docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PASSWORD}" "${DST_REGISTRY}" +docker push "${DST_IMG}" From c1861777a1f23030ca35dd184df75919dfc0c21d Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 31 Jul 2024 10:48:55 -0700 Subject: [PATCH 014/136] Remove unneeded file --- .tool-versions | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 09548d5..0000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -golang 1.19.13 From e0e45d830e36fdf5e14a46e511d5c7a493202a18 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 19 Aug 2024 16:21:40 -0700 Subject: [PATCH 015/136] Capture code coverage go test does not have a good way to capture unit-test coverage as part of test runs. This commit captures the error code of the unit test run, runs the coverage generation and then exits based on the test status. --- .gitlab-ci.yml | 2 +- scripts/test.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d59d1bb..fdb3481 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -104,7 +104,7 @@ unit-test-data-race: always paths: - results - expire_in: 3 hours + expire_in: 1 day reports: junit: results/report.xml rules: diff --git a/scripts/test.sh b/scripts/test.sh index 107cee4..00ca02d 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -10,7 +10,12 @@ fi outfile="${RESULTS_DIR}/coverage.out" mkdir -p "$RESULTS_DIR" go_flags+=("-cover" -coverprofile="$outfile") + +set +e gotestsum --junitfile "${RESULTS_DIR}/report.xml" --format pkgname -- "${go_flags[@]}" ./... +rc=$? +set -e echo "Total code coverage:" go tool cover -func="$outfile" | grep 'total:' | tee "${RESULTS_DIR}/anybadge.out" go tool cover -html="$outfile" -o "${RESULTS_DIR}/coverage.html" +exit $rc From ea11e79fea3a4d9d5b4d8913266c99466c9c68b2 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 19 Aug 2024 18:29:29 -0700 Subject: [PATCH 016/136] Run coverage in a separate job We want to avoid running tests several times on main as it increases the time it takes to publish an image on regular merges. Instead, we can run these tests on a schedule on main and capture code coverage from it. --- .gitlab-ci.yml | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fdb3481..92e82f6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,6 +58,25 @@ workflow: go env -w GOPRIVATE="gitlab.com/f5" go env +.unit-test-common: + stage: lint+test+build + image: $DEVTOOLS_IMG + extends: + - .default-runner-large + # - .go-cache-readonly + script: + - *golang-private + - time make test + coverage: '/^total:\s+\(statements\)\s+(\d+\.\d+\%)$/' + artifacts: + when: + always + paths: + - results + expire_in: 1 day + reports: + junit: results/report.xml + lint + unit-test + build: stage: lint+test+build image: $DEVTOOLS_IMG @@ -88,28 +107,19 @@ lint + unit-test + build: - if: '$CI_COMMIT_BRANCH || $CI_MERGE_REQUEST_ID' unit-test-data-race: - stage: lint+test+build - image: $DEVTOOLS_IMG variables: GO_DATA_RACE: "true" extends: - - .default-runner-large - # - .go-cache-readonly - script: - - *golang-private - - time make test - coverage: '/^total:\s+\(statements\)\s+(\d+\.\d+\%)$/' - artifacts: - when: - always - paths: - - results - expire_in: 1 day - reports: - junit: results/report.xml + - .unit-test-common rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $GO_DATA_RACE == "true"' +coverage: + extends: + - .unit-test-common + rules: + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule"' + whitesource-scan: stage: lint+test+build extends: From bd184eb18f271902aebe45f930820398c84c813c Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 22 Aug 2024 13:15:39 -0600 Subject: [PATCH 017/136] NLB-5360 Upgraded nginx-plus client to v 1.2.2 --- go.mod | 6 ++++-- go.sum | 4 ++-- internal/synchronization/synchronizer.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7a95a39..118de5f 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.21 +go 1.21.2 + +toolchain go1.21.4 require ( - github.com/nginxinc/nginx-plus-go-client v0.10.0 + github.com/nginxinc/nginx-plus-go-client v1.2.2 github.com/sirupsen/logrus v1.9.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 diff --git a/go.sum b/go.sum index 7d3d0e5..46a175a 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nginxinc/nginx-plus-go-client v0.10.0 h1:3zsMMkPvRDo8D7ZSprXtbAEW/SDmezZWzxdyS+6oAlc= -github.com/nginxinc/nginx-plus-go-client v0.10.0/go.mod h1:0v3RsQCvRn/IyrMtW+DK6CNkz+PxEsXDJPjQ3yUMBF0= +github.com/nginxinc/nginx-plus-go-client v1.2.2 h1:sl7HqNDDZq2EVu0eQQVoZ6PKYGa4h2dB/Qr5Ib0YKGw= +github.com/nginxinc/nginx-plus-go-client v1.2.2/go.mod h1:n8OFLzrJulJ2fur28Cwa1Qp5DZNS2VicLV+Adt30LQ4= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 9d1a697..5280726 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -119,7 +119,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica return nil, fmt.Errorf(`error creating HTTP client: %v`, err) } - ngxClient, err := nginxClient.NewNginxClient(httpClient, event.NginxHost) + ngxClient, err := nginxClient.NewNginxClient(event.NginxHost, nginxClient.WithHTTPClient(httpClient)) if err != nil { return nil, fmt.Errorf(`error creating Nginx Plus client: %v`, err) } From e613e41693d51cf5b38d83dd8210eea5fd2316fc Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 31 Jul 2024 15:58:35 -0600 Subject: [PATCH 018/136] NLB-5065 Operator adds API Key to header --- internal/communication/factory.go | 13 ++++++--- internal/communication/factory_test.go | 29 +++++++++++++++++++-- internal/communication/roundtripper_test.go | 16 +++++++----- internal/configuration/settings.go | 9 +++++++ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/internal/communication/factory.go b/internal/communication/factory.go index 40bf02a..64ffe68 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -7,6 +7,7 @@ package communication import ( "crypto/tls" + "fmt" netHttp "net/http" "time" @@ -19,7 +20,7 @@ import ( // RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, // the Headers are configured for JSON. func NewHTTPClient(settings *configuration.Settings) (*netHttp.Client, error) { - headers := NewHeaders() + headers := NewHeaders(settings.APIKey) tlsConfig := NewTLSConfig(settings) transport := NewTransport(tlsConfig) roundTripper := NewRoundTripper(headers, transport) @@ -33,11 +34,17 @@ func NewHTTPClient(settings *configuration.Settings) (*netHttp.Client, error) { } // NewHeaders is a factory method to create a new basic Http Headers slice. -func NewHeaders() []string { - return []string{ +func NewHeaders(apiKey string) []string { + headers := []string{ "Content-Type: application/json", "Accept: application/json", } + + if apiKey != "" { + headers = append(headers, fmt.Sprintf("Authorization: ApiKey %s", apiKey)) + } + + return headers } // NewTLSConfig is a factory method to create a new basic Tls Config. diff --git a/internal/communication/factory_test.go b/internal/communication/factory_test.go index 375da3b..398bb6c 100644 --- a/internal/communication/factory_test.go +++ b/internal/communication/factory_test.go @@ -21,7 +21,6 @@ func TestNewHTTPClient(t *testing.T) { t.Fatalf(`Unexpected error: %v`, err) } client, err := NewHTTPClient(settings) - if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -31,9 +30,35 @@ func TestNewHTTPClient(t *testing.T) { } } +//nolint:goconst func TestNewHeaders(t *testing.T) { t.Parallel() - headers := NewHeaders() + headers := NewHeaders("fakeKey") + + if headers == nil { + t.Fatalf(`headers should not be nil`) + } + + if len(headers) != 3 { + t.Fatalf(`headers should have 3 elements`) + } + + if headers[0] != "Content-Type: application/json" { + t.Fatalf(`headers[0] should be "Content-Type: application/json"`) + } + + if headers[1] != "Accept: application/json" { + t.Fatalf(`headers[1] should be "Accept: application/json"`) + } + + if headers[2] != "Authorization: ApiKey fakeKey" { + t.Fatalf(`headers[2] should be "Accept: Authorization: ApiKey fakeKey"`) + } +} + +func TestNewHeadersWithNoAPIKey(t *testing.T) { + t.Parallel() + headers := NewHeaders("") if headers == nil { t.Fatalf(`headers should not be nil`) diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index ff6d5c4..abee455 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -19,7 +19,7 @@ func TestNewRoundTripper(t *testing.T) { t.Parallel() k8sClient := fake.NewSimpleClientset() settings, _ := configuration.NewSettings(context.Background(), k8sClient) - headers := NewHeaders() + headers := NewHeaders("fakeKey") transport := NewTransport(NewTLSConfig(settings)) roundTripper := NewRoundTripper(headers, transport) @@ -31,8 +31,8 @@ func TestNewRoundTripper(t *testing.T) { t.Fatalf(`roundTripper.Headers should not be nil`) } - if len(roundTripper.Headers) != 2 { - t.Fatalf(`roundTripper.Headers should have 2 elements`) + if len(roundTripper.Headers) != 3 { + t.Fatalf(`roundTripper.Headers should have 3 elements`) } if roundTripper.Headers[0] != "Content-Type: application/json" { @@ -43,6 +43,10 @@ func TestNewRoundTripper(t *testing.T) { t.Fatalf(`roundTripper.Headers[1] should be "Accept: application/json"`) } + if roundTripper.Headers[2] != "Authorization: ApiKey fakeKey" { + t.Fatalf(`headers[2] should be "Accept: Authorization: ApiKey fakeKey"`) + } + if roundTripper.RoundTripper == nil { t.Fatalf(`roundTripper.RoundTripper should not be nil`) } @@ -55,7 +59,7 @@ func TestRoundTripperRoundTrip(t *testing.T) { if err != nil { t.Fatalf(`Unexpected error: %v`, err) } - headers := NewHeaders() + headers := NewHeaders("fakeKey") transport := NewTransport(NewTLSConfig(settings)) roundTripper := NewRoundTripper(headers, transport) @@ -78,8 +82,8 @@ func TestRoundTripperRoundTrip(t *testing.T) { defer response.Body.Close() headerLen := len(response.Header) - if headerLen <= 2 { - t.Fatalf(`response.Header should have at least 2 elements, found %d`, headerLen) + if headerLen <= 3 { + t.Fatalf(`response.Header should have at least 3 elements, found %d`, headerLen) } } diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 857bd43..400b311 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -7,7 +7,9 @@ package configuration import ( "context" + "encoding/base64" "fmt" + "os" "strings" "time" @@ -119,6 +121,9 @@ type Settings struct { // with the Border Servers (see: ../../docs/tls/README.md). TLSMode TLSMode + // APIKey is the api key used to authenticate with the dataplane API. + APIKey string + // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. Certificates *certification.Certificates @@ -143,10 +148,14 @@ type Settings struct { // NewSettings creates a new Settings object with default values. func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings, error) { + // get base64 encoded version of raw api key set by user + apiKey := base64.StdEncoding.EncodeToString([]byte(os.Getenv("NGINXAAS_DATAPLANE_API_KEY"))) + settings := &Settings{ Context: ctx, K8sClient: k8sClient, TLSMode: NoTLS, + APIKey: apiKey, Certificates: nil, Handler: HandlerSettings{ RetryCount: 5, From 713b26c2d7295cffaee20b56816f4eb0445ccf54 Mon Sep 17 00:00:00 2001 From: sarna Date: Sun, 1 Sep 2024 19:57:16 -0700 Subject: [PATCH 019/136] Publish helm charts for development Helm will be used as part of the user story to deploy the operator but it is also a good tool to deploy the operator while developing it. This commit adds the ability to publish helm charts: - to the dev registry for local iteration. - to the regular devops registry for CI iteration and testing. This will also help us test the helm chart itself. --- .gitignore | 2 ++ .gitlab-ci.yml | 1 + Makefile | 4 ++++ scripts/publish-helm.sh | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100755 scripts/publish-helm.sh diff --git a/.gitignore b/.gitignore index 4e1c0fd..396ec9f 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,5 @@ results .go/pkg/mod .go-build + +nginx-loadbalancer-kubernetes-* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 92e82f6..610889b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -92,6 +92,7 @@ lint + unit-test + build: time make test fi time make publish + time make publish-helm coverage: '/^total:\s+\(statements\)\s+(\d+\.\d+\%)$/' artifacts: when: diff --git a/Makefile b/Makefile index 4d404ac..2795d83 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,10 @@ build-linux: build-linux-docker: @./scripts/docker.sh build +publish-helm: + @scripts/docker-login.sh + @scripts/publish-helm.sh + publish: build-linux build-linux-docker @scripts/docker-login.sh @./scripts/docker.sh publish diff --git a/scripts/publish-helm.sh b/scripts/publish-helm.sh new file mode 100755 index 0000000..45e94aa --- /dev/null +++ b/scripts/publish-helm.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -eo pipefail + +ROOT_DIR=$(git rev-parse --show-toplevel) + +publish_helm() { + pkg="nginx-loadbalancer-kubernetes-${VERSION}.tgz" + helm package --version "${VERSION}" --app-version "${VERSION}" charts/nlk + helm push "${pkg}" "${repo}" +} + +init_ci_vars() { + if [ -z "$CI_PROJECT_NAME" ]; then + CI_PROJECT_NAME=$(basename "$ROOT_DIR") + fi + if [ -z "$CI_COMMIT_REF_SLUG" ]; then + CI_COMMIT_REF_SLUG=$( + git rev-parse --abbrev-ref HEAD | tr "[:upper:]" "[:lower:]" \ + | LANG=en_US.utf8 sed -E -e 's/[^a-zA-Z0-9]/-/g' -e 's/^-+|-+$$//g' \ + | cut -c 1-63 + ) + fi +} + +# MAIN +init_ci_vars + +# shellcheck source=/dev/null +source "${ROOT_DIR}/.devops.sh" +if [ "$CI" != "true" ]; then + devops.backend.docker.set "azure.container-registry-dev" +fi +repo="oci://${DEVOPS_DOCKER_URL}/nginx-azure-lb/${CI_PROJECT_NAME}/charts/${CI_COMMIT_REF_SLUG}" +# shellcheck source=/dev/null +# shellcheck disable=SC2153 +version=$(source "${ROOT_DIR}/version";echo "$VERSION") + +publish_helm From 294986848f5dd100f21cc01c2558415206a6f2ac Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 22 Aug 2024 17:44:57 -0700 Subject: [PATCH 020/136] Update Chart version to be 0.1.0 0.0.1 is okay but it's not really a bug fix. --- charts/nlk/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index c11d885..e82eaab 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -16,4 +16,4 @@ maintainers: - name: "@abdennour" type: application -version: 0.0.1 +version: 0.1.0 From 608f0081b4df04f3c3ff9a244b4e2adf320a50f6 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 26 Aug 2024 12:57:00 -0700 Subject: [PATCH 021/136] Update release script to handle dual artifacts We should keep a single release script that will publish docker images and helm charts for the official release. This commit just updates the current release script to handle both artifact types. Helm logic will follow. --- .gitlab-ci.yml | 2 +- scripts/release.sh | 113 ++++++++++++++++++++++++++++----------------- 2 files changed, 71 insertions(+), 44 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 610889b..d4c593b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -195,6 +195,6 @@ dockerhub-push: extends: - .devops-docker-cicd script: - - ./scripts/release.sh + - ./scripts/release.sh docker-image rules: - if: '$CI_COMMIT_TAG =~ /^release-[\d]+\.[\d]+\.[\d]+/' diff --git a/scripts/release.sh b/scripts/release.sh index 7c85792..48b18a8 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -2,46 +2,73 @@ set -eo pipefail -if [[ "${CI}" != "true" ]]; then - echo "This script is meant to be run in the CI." - exit 1 -fi - -pttn="^release-[0-9]+\.[0-9]+\.[0-9]+" -if ! [[ "${CI_COMMIT_TAG}" =~ $pttn ]]; then - echo "CI_COMMIT_TAG needs to be set to valid semver format." - exit 1 -fi - -ROOT_DIR=$(git rev-parse --show-toplevel) -source ${ROOT_DIR}/.devops.sh - -DOCKERHUB_USERNAME=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".username") -if [[ -z "${DOCKERHUB_USERNAME}" ]]; then - echo "DOCKERHUB_USERNAME needs to be set." - exit 1 -fi - -DOCKERHUB_PASSWORD=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".password") -if [[ -z "${DOCKERHUB_PASSWORD}" ]]; then - echo "DOCKERHUB_PASSWORD needs to be set." - exit 1 -fi - -SRC_REGISTRY="${DEVOPS_DOCKER_URL}" -SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" -SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") -SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" - -DST_REGISTRY="docker.io" -DST_PATH="nginx/nginxaas-operator" -DST_TAG="${CI_COMMIT_TAG}" -DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" - -devops.docker.login -docker pull "${SRC_IMG}" -docker tag "${SRC_IMG}" "${DST_IMG}" - -# Login to Dockerhub and push release image to it. -docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PASSWORD}" "${DST_REGISTRY}" -docker push "${DST_IMG}" +docker-image() { + DOCKERHUB_USERNAME=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".username") + if [[ -z "${DOCKERHUB_USERNAME}" ]]; then + echo "DOCKERHUB_USERNAME needs to be set." + exit 1 + fi + + DOCKERHUB_PASSWORD=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".password") + if [[ -z "${DOCKERHUB_PASSWORD}" ]]; then + echo "DOCKERHUB_PASSWORD needs to be set." + exit 1 + fi + + SRC_REGISTRY="${DEVOPS_DOCKER_URL}" + SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" + SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") + SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" + + DST_REGISTRY="docker.io" + DST_PATH="nginx/nginxaas-operator" + DST_TAG="${CI_COMMIT_TAG}" + DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" + + devops.docker.login + docker pull "${SRC_IMG}" + docker tag "${SRC_IMG}" "${DST_IMG}" + + # Login to Dockerhub and push release image to it. + docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PASSWORD}" "${DST_REGISTRY}" + docker push "${DST_IMG}" +} + + +help_text() { + echo "Usage: $(basename $0) " +} + +parse_args() { + if [[ "$#" -ne 1 ]]; then + help_text + exit 0 + fi + + artifact="${1}" + valid_artifact="(docker-image|helm-chart)" + valid_artifact_pttn="^${valid_artifact}$" + if ! [[ "${artifact}" =~ $valid_artifact_pttn ]]; then + echo "Invalid artifact type. Valid artifact types: $valid_artifact" + help_text + exit 1 + fi +} + +main() { + if [[ "${CI}" != "true" ]]; then + echo "This script is meant to be run in the CI." + exit 1 + fi + pttn="^release-[0-9]+\.[0-9]+\.[0-9]+" + if ! [[ "${CI_COMMIT_TAG}" =~ $pttn ]]; then + echo "CI_COMMIT_TAG needs to be set to valid semver format." + exit 1 + fi + parse_args "$@" + ROOT_DIR=$(git rev-parse --show-toplevel) + source ${ROOT_DIR}/.devops.sh + "$artifact" +} + +main "$@" From 71b9abeb6f68ffcccff785bc6c2c2797e34de252 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 26 Aug 2024 16:28:36 -0700 Subject: [PATCH 022/136] Add helm chart publishing for a release This commit adds logic to publish helm charts to dockerhub using it as an OCI helm registry. Moved around common docker code into its own function to reuse logic across helm and docker publishing. Not worrying about chart versioning right now and just setting up the CI for publishing. --- scripts/release.sh | 51 ++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 48b18a8..3068b79 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -3,6 +3,36 @@ set -eo pipefail docker-image() { + SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" + SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") + SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" + DST_PATH="nginx/nginxaas-operator" + DST_TAG="${CI_COMMIT_TAG}" + DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" + + docker pull "${SRC_IMG}" + docker tag "${SRC_IMG}" "${DST_IMG}" + docker push "${DST_IMG}" +} + +helm-chart() { + SRC_PATH="nginxazurelb/charts/nginx-loadbalancer-kubernetes" + SRC_TAG="0.1.0" + SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" + DST_PATH="nginxinc/charts" + DST_TAG="0.1.0" + DST_CHART="oci://${DST_REGISTRY}/${DST_PATH}" + + helm pull "${SRC_CHART}" --version "${SRC_TAG}" + helm push nginx-loadbalancer-kubernetes-${DST_TAG}.tgz "${DST_CHART}" +} + + +help_text() { + echo "Usage: $(basename $0) " +} + +set_docker_common() { DOCKERHUB_USERNAME=$(devops.secret.get "kic-dockerhub-creds" | jq -r ".username") if [[ -z "${DOCKERHUB_USERNAME}" ]]; then echo "DOCKERHUB_USERNAME needs to be set." @@ -14,29 +44,13 @@ docker-image() { echo "DOCKERHUB_PASSWORD needs to be set." exit 1 fi - SRC_REGISTRY="${DEVOPS_DOCKER_URL}" - SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" - SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") - SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" - DST_REGISTRY="docker.io" - DST_PATH="nginx/nginxaas-operator" - DST_TAG="${CI_COMMIT_TAG}" - DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" + # Login to NGINX DevOps Registry. devops.docker.login - docker pull "${SRC_IMG}" - docker tag "${SRC_IMG}" "${DST_IMG}" - - # Login to Dockerhub and push release image to it. + # Login to Dockerhub. docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PASSWORD}" "${DST_REGISTRY}" - docker push "${DST_IMG}" -} - - -help_text() { - echo "Usage: $(basename $0) " } parse_args() { @@ -68,6 +82,7 @@ main() { parse_args "$@" ROOT_DIR=$(git rev-parse --show-toplevel) source ${ROOT_DIR}/.devops.sh + set_docker_common "$artifact" } From 86f8dda2d330382e823c7511a40408047c24bce1 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 26 Aug 2024 16:33:02 -0700 Subject: [PATCH 023/136] Set up CI to do helm+docker releases --- .gitlab-ci.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d4c593b..fbc5fcb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -189,12 +189,24 @@ tag: when: never - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' -dockerhub-push: +.release-common: stage: release image: $DEVTOOLS_IMG extends: - .devops-docker-cicd - script: - - ./scripts/release.sh docker-image rules: - if: '$CI_COMMIT_TAG =~ /^release-[\d]+\.[\d]+\.[\d]+/' + +dockerhub-image-release: + extends: + - .devops-docker-cicd + - .release-common + script: + - ./scripts/release.sh docker-image + +dockerhub-helm-release: + extends: + - .devops-docker-cicd + - .release-common + script: + - ./scripts/release.sh helm-chart From 1f7ff6a0506a308bf3070a7a323d55407817152c Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Mon, 16 Sep 2024 09:12:00 -0600 Subject: [PATCH 024/136] NLB-5549 Translator allows hyphens in upstream name Previously, owing to a bug, if the name of the upstream included hyphens it would be rejected by the operator. --- internal/translation/translator.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/translation/translator.go b/internal/translation/translator.go index 9dad7f8..fe8532c 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -79,15 +79,14 @@ func buildUpstreamServers(nodeIPs []string, port v1.ServicePort) core.UpstreamSe // getContextAndUpstreamName returns the nginx context being supplied by the port (either "http" or "stream") // and the upstream name. func getContextAndUpstreamName(port v1.ServicePort) (clientType string, appName string, err error) { - parts := strings.Split(port.Name, "-") - if len(parts) != 2 { + context, upstreamName, found := strings.Cut(port.Name, "-") + switch { + case !found: return clientType, appName, fmt.Errorf("ignoring port %s because it is not in the format [http|stream]-{upstreamName}", port.Name) - } - - if parts[0] != "http" && parts[0] != "stream" { + case context != "http" && context != "stream": return clientType, appName, fmt.Errorf("port name %s does not include \"http\" or \"stream\" context", port.Name) + default: + return context, upstreamName, nil } - - return parts[0], parts[1], nil } From 26c861b87491cf9193a71d8f4bd0fce1130e249e Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 12 Sep 2024 09:24:16 -0700 Subject: [PATCH 025/136] Fix path to pull helm charts Helm charts are published under `nginx-azure-lb` alongside docker images for all of our services. In addition to the above, charts are published keeping in mind that each feature branch iteration can produce a helm chart. While doing a release, we need to pull in the main chart and then re-tag it to push to docker hub. --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 3068b79..089bb4b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -16,7 +16,7 @@ docker-image() { } helm-chart() { - SRC_PATH="nginxazurelb/charts/nginx-loadbalancer-kubernetes" + SRC_PATH="nginx-azure-lb/nginxaas-operator/charts/main/nginx-loadbalancer-kubernetes" SRC_TAG="0.1.0" SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" DST_PATH="nginxinc/charts" From 1f669dd2c45fca8807e5115439140ec153873a8b Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 12 Sep 2024 09:36:55 -0700 Subject: [PATCH 026/136] Keep major version to 0 We are going to release with `0.x.y` and keeping the internal version inline with what will get published out will reduce confusion. --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index e0212c0..a8eecae 100644 --- a/version +++ b/version @@ -1 +1 @@ -export VERSION="1.$(date +"%Y%m%d").${CI_PIPELINE_ID:-0}" +export VERSION="0.$(date +"%Y%m%d").${CI_PIPELINE_ID:-0}" From 8432c0abe987bba665b82be7695c3481d6fe718d Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 19 Sep 2024 04:18:13 -0700 Subject: [PATCH 027/136] Fix how chart gets pushed to remote Version is not specified like we usually specify docker tags. Instead, the version is checked with what's specified inside the chart. --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 089bb4b..9d4bd40 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -18,7 +18,7 @@ docker-image() { helm-chart() { SRC_PATH="nginx-azure-lb/nginxaas-operator/charts/main/nginx-loadbalancer-kubernetes" SRC_TAG="0.1.0" - SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" + SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}" DST_PATH="nginxinc/charts" DST_TAG="0.1.0" DST_CHART="oci://${DST_REGISTRY}/${DST_PATH}" From d70bfb64f5e7ec3427c0a49f43f2461050e158e5 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 19 Sep 2024 04:25:13 -0700 Subject: [PATCH 028/136] Fix src image tag Images are tagged as "main-". --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 9d4bd40..42f5073 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -5,7 +5,7 @@ set -eo pipefail docker-image() { SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") - SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:${SRC_TAG}" + SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:main-${SRC_TAG}" DST_PATH="nginx/nginxaas-operator" DST_TAG="${CI_COMMIT_TAG}" DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" From 3a134e8edd0d9825d2d18566e10800d6cb90a8a8 Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 20 Sep 2024 16:26:55 -0600 Subject: [PATCH 029/136] Use semantic versioning This repository is going to produce artifacts that will be available publicly and the end users will care about semantic versioning. We need to be able to map a public facing version to internally produced artifacts easily and having semver internally eases that work. This commit does not enforce the versioning but adds a version file that has the semver, which will be used to version the product. We can follow a workflow where during release time, we cut a release, which creates a tag and we retag existing dev artifacts to be shipped as an official artifact. --- Makefile | 2 +- scripts/build.sh | 2 +- scripts/docker.sh | 2 +- scripts/publish-helm.sh | 6 +++--- version | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 2795d83..f0c88a8 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ BUILD_DIR = build export BUILD_DIR RESULTS_DIR = results export RESULTS_DIR -VERSION = $(shell bash -c 'source version; echo $$VERSION') +VERSION = $(shell cat version) export VERSION DOCKER_REGISTRY ?= local diff --git a/scripts/build.sh b/scripts/build.sh index b5be0ed..aafb418 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -9,7 +9,7 @@ if [[ -z $CI_COMMIT_SHORT_SHA ]]; then CI_COMMIT_SHORT_SHA=$(git rev-parse --short=8 HEAD) fi if [[ -z $VERSION ]]; then - VERSION=$(source version;echo "$VERSION") + VERSION=$(cat version) fi if [ "$os" == "linux" ]; then diff --git a/scripts/docker.sh b/scripts/docker.sh index 4193f55..af473f1 100755 --- a/scripts/docker.sh +++ b/scripts/docker.sh @@ -81,6 +81,6 @@ fi repo="${DEVOPS_DOCKER_URL}/nginx-azure-lb/${CI_PROJECT_NAME}/$image" # shellcheck source=/dev/null # shellcheck disable=SC2153 -version=$(source "${ROOT_DIR}/version";echo "$VERSION") +version=$(cat version) "$action" diff --git a/scripts/publish-helm.sh b/scripts/publish-helm.sh index 45e94aa..7d3de56 100755 --- a/scripts/publish-helm.sh +++ b/scripts/publish-helm.sh @@ -5,8 +5,8 @@ set -eo pipefail ROOT_DIR=$(git rev-parse --show-toplevel) publish_helm() { - pkg="nginx-loadbalancer-kubernetes-${VERSION}.tgz" - helm package --version "${VERSION}" --app-version "${VERSION}" charts/nlk + pkg="nginx-loadbalancer-kubernetes-${version}.tgz" + helm package --version "${version}" --app-version "${version}" charts/nlk helm push "${pkg}" "${repo}" } @@ -34,6 +34,6 @@ fi repo="oci://${DEVOPS_DOCKER_URL}/nginx-azure-lb/${CI_PROJECT_NAME}/charts/${CI_COMMIT_REF_SLUG}" # shellcheck source=/dev/null # shellcheck disable=SC2153 -version=$(source "${ROOT_DIR}/version";echo "$VERSION") +version=$(cat version) publish_helm diff --git a/version b/version index a8eecae..6e8bf73 100644 --- a/version +++ b/version @@ -1 +1 @@ -export VERSION="0.$(date +"%Y%m%d").${CI_PIPELINE_ID:-0}" +0.1.0 From d972271408191bfca122335d69e752a17af9e3b2 Mon Sep 17 00:00:00 2001 From: sarna Date: Sat, 21 Sep 2024 17:56:38 -0600 Subject: [PATCH 030/136] Remove auto tagging To follow a simple process of mapping external releases to internal artifacts, a simple semver will help. This means that auto-creating tags on merges to main from the semver will lead to confusion. --- .gitlab-ci.yml | 12 ------------ scripts/release-tag.sh | 18 ------------------ 2 files changed, 30 deletions(-) delete mode 100755 scripts/release-tag.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fbc5fcb..fe36d93 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -177,18 +177,6 @@ checkmarx-scan: needs: [] allow_failure: true -tag: - stage: release - image: $DEVTOOLS_IMG - extends: - - .default-runner - script: - - ./scripts/release-tag.sh - rules: - - if: '$CI_PIPELINE_SOURCE == "schedule"' - when: never - - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - .release-common: stage: release image: $DEVTOOLS_IMG diff --git a/scripts/release-tag.sh b/scripts/release-tag.sh deleted file mode 100755 index 8c6d639..0000000 --- a/scripts/release-tag.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -### This script should only run during master pipelines ### -### This script will create tags for the master commit using the current version ### - -set -eo pipefail - -if [ "$CI_COMMIT_REF_NAME" = "main" ]; then - # shellcheck source=/dev/null - version_tag=v$(source version;echo "$VERSION") - - curl -s \ - --request POST \ - --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN_RW}" \ - "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/repository/tags" \ - --form "tag_name=$version_tag" \ - --form "ref=$CI_COMMIT_SHA" -fi From f1d7238bf1b8bd9fc77fb06bda5478c9d30b7e90 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 23 Sep 2024 11:52:41 -0700 Subject: [PATCH 031/136] Allow CI to run on tags --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fe36d93..d3d94f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,7 @@ workflow: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never - - if: $CI_COMMIT_BRANCH + - if: '$CI_COMMIT_BRANCH || $CI_COMMIT_TAG' .import-devops-core-services: &import-devops-core-services | source ${CI_PROJECT_DIR}/.devops.sh From b49b4adc0ff76a5d47b8a7905917cea84060caa2 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 23 Sep 2024 14:20:53 -0700 Subject: [PATCH 032/136] Fix path to push chart Charts go under https://hub.docker.com/orgs/nginxcharts --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 42f5073..9f79999 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -19,7 +19,7 @@ helm-chart() { SRC_PATH="nginx-azure-lb/nginxaas-operator/charts/main/nginx-loadbalancer-kubernetes" SRC_TAG="0.1.0" SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}" - DST_PATH="nginxinc/charts" + DST_PATH="nginxcharts" DST_TAG="0.1.0" DST_CHART="oci://${DST_REGISTRY}/${DST_PATH}" From f3e233e4b958bdc935742d254f42d1bdeae46f0b Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 24 Sep 2024 11:05:05 -0700 Subject: [PATCH 033/136] Rename the chart to nginxaas-operator While a cosmetic change, it does impact how dokerhub repo needs to be setup to publish the helm chart. In addition to that, it impacts what the user see on k8s itself and it should not be nginx-loadbalancer-kubernetes as that is confusing. --- charts/nlk/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index e82eaab..1f986a3 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 appVersion: 0.1.0 description: NGINX LoadBalancer for Kubernetes -name: nginx-loadbalancer-kubernetes +name: nginxaas-operator home: https://github.com/nginxinc/nginx-loadbalancer-kubernetes icon: https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/main/nlk-logo.svg keywords: From 2cde46d885f1e279c51a5409ac11d5f16fadfcf4 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 24 Sep 2024 13:04:36 -0700 Subject: [PATCH 034/136] Update name to be operator While the chartname (which was renamed in the prior commit) gets used for naming stuff, it makes sense to also change the name value in the chart itself to use the new name. --- charts/nlk/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 394bc1f..ab957a6 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -1,5 +1,5 @@ nlk: - name: nginx-loadbalancer-kubernetes + name: nginxaas-operator kind: deployment From e292269480736028a4267f36f4ca5747fd7ad666 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 24 Sep 2024 13:06:33 -0700 Subject: [PATCH 035/136] Publish chart with new name --- scripts/publish-helm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/publish-helm.sh b/scripts/publish-helm.sh index 7d3de56..8abecc5 100755 --- a/scripts/publish-helm.sh +++ b/scripts/publish-helm.sh @@ -5,7 +5,7 @@ set -eo pipefail ROOT_DIR=$(git rev-parse --show-toplevel) publish_helm() { - pkg="nginx-loadbalancer-kubernetes-${version}.tgz" + pkg="nginxaas-operator-${version}.tgz" helm package --version "${version}" --app-version "${version}" charts/nlk helm push "${pkg}" "${repo}" } From 630179f1552568cda1be43569eedf378d638ac57 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 24 Sep 2024 13:07:39 -0700 Subject: [PATCH 036/136] Use new name for publishing release artifacts Once the rename occurs, we need to update the release logic to pull the newly named chart, and push it up to dockerhub. --- scripts/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 9f79999..aa942c8 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -16,7 +16,7 @@ docker-image() { } helm-chart() { - SRC_PATH="nginx-azure-lb/nginxaas-operator/charts/main/nginx-loadbalancer-kubernetes" + SRC_PATH="nginx-azure-lb/nginxaas-operator/charts/main/nginxaas-operator" SRC_TAG="0.1.0" SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}" DST_PATH="nginxcharts" @@ -24,7 +24,7 @@ helm-chart() { DST_CHART="oci://${DST_REGISTRY}/${DST_PATH}" helm pull "${SRC_CHART}" --version "${SRC_TAG}" - helm push nginx-loadbalancer-kubernetes-${DST_TAG}.tgz "${DST_CHART}" + helm push nginxaas-operator-${DST_TAG}.tgz "${DST_CHART}" } From dca1ce58d792562a21d3f9dae26dfef976679659 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 24 Sep 2024 21:05:34 -0700 Subject: [PATCH 037/136] Update registry paths to dockerhub Now that official images exist on docker hub, we should use those images in our charts. --- charts/nlk/values.yaml | 36 ++++++++++++++++++------------------ version | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index ab957a6..11d082b 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -4,18 +4,18 @@ nlk: kind: deployment replicaCount: 1 - + image: - registry: ghcr.io - repository: nginxinc/nginx-loadbalancer-kubernetes + registry: registry-1.docker.io + repository: nginx/nginxaas-operator pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. - tag: latest - + tag: release-0.1.0 + imagePullSecrets: [] nameOverride: "" fullnameOverride: "" - + serviceAccount: # Specifies whether a service account should be created create: true @@ -23,13 +23,13 @@ nlk: automount: true # Annotations to add to the service account annotations: {} - + podAnnotations: {} podLabels: {} - + podSecurityContext: {} # fsGroup: 2000 - + securityContext: {} # capabilities: # drop: @@ -37,11 +37,11 @@ nlk: # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 - + service: type: ClusterIP port: 80 - + ingress: enabled: false className: "" @@ -57,7 +57,7 @@ nlk: # - secretName: chart-example-tls # hosts: # - chart-example.local - + resources: requests: cpu: 100m @@ -65,31 +65,31 @@ nlk: # limits: # cpu: 100m # memory: 128Mi - + autoscaling: enabled: false minReplicas: 1 maxReplicas: 3 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 - + # Additional volumes on the output Deployment definition. volumes: [] # - name: foo # secret: # secretName: mysecret # optional: false - + # Additional volumeMounts on the output Deployment definition. volumeMounts: [] # - name: foo # mountPath: "/etc/foo" # readOnly: true - + nodeSelector: {} - + tolerations: [] - + affinity: {} config: diff --git a/version b/version index 6e8bf73..d917d3e 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.1.0 +0.1.2 From c25d857eead971ad11cd453e648203697500567c Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 24 Sep 2024 21:06:37 -0700 Subject: [PATCH 038/136] Update gitignore with new chart name Now that the chart has been renamed, gitignore needs to know about ignoring new charts. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 396ec9f..f434c43 100644 --- a/.gitignore +++ b/.gitignore @@ -88,4 +88,4 @@ results .go-build -nginx-loadbalancer-kubernetes-* +nginxaas-operator-* From 7dd452af0a471053d78da8104cd488d685f1bd3e Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 25 Sep 2024 00:40:39 -0700 Subject: [PATCH 039/136] Add support for image pull secrets This lets us test the operator from the private registry. Also, in case a customer does not want to pull from docker hub and instead use their own registry, they can do so and specify a pull secret for the image. --- charts/nlk/templates/nlk-deployment.yaml | 4 ++++ version | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index fb55d77..2653d0b 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -15,6 +15,10 @@ spec: labels: app: nlk spec: + {{- with .Values.nlk.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: {{ .Chart.Name }} image: {{ include "nlk.image" .}} diff --git a/version b/version index d917d3e..b1e80bb 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.1.2 +0.1.3 From 83ce3b6c56e53cc845632bbfc638669a8ea2b843 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 26 Sep 2024 19:13:06 -0700 Subject: [PATCH 040/136] Mount configmap as a volume With the change to make the operator read a config file (to reduce code complexity), we need to make sure that the file exists for the service to start. --- charts/nlk/templates/nlk-configmap.yaml | 12 ++++++------ charts/nlk/templates/nlk-deployment.yaml | 7 +++++++ version | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml index 482b8cb..ab8977f 100644 --- a/charts/nlk/templates/nlk-configmap.yaml +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -4,11 +4,11 @@ metadata: name: nlk-config namespace: nlk data: + config.yaml: | {{- if .Values.nlk.config.entries.hosts }} - nginx-hosts: "{{ .Values.nlk.config.entries.hosts }}" + nginx-hosts: "{{ .Values.nlk.config.entries.hosts }}" {{- end }} - tls-mode: "{{ index .Values.nlk.defaultTLS "tls-mode" }}" - ca-certificate: "{{ index .Values.nlk.defaultTLS "ca-certificate" }}" - client-certificate: "{{ index .Values.nlk.defaultTLS "client-certificate" }}" - log-level: "{{ .Values.nlk.logLevel }}" - + tls-mode: "{{ index .Values.nlk.defaultTLS "tls-mode" }}" + ca-certificate: "{{ index .Values.nlk.defaultTLS "ca-certificate" }}" + client-certificate: "{{ index .Values.nlk.defaultTLS "client-certificate" }}" + log-level: "{{ .Values.nlk.logLevel }}" diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index 2653d0b..3b5348e 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -45,4 +45,11 @@ spec: initialDelaySeconds: {{ .Values.nlk.readyStatus.initialDelaySeconds }} periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} {{- end }} + volumeMounts: + - name: config + mountPath: /etc/nginxaas-operator serviceAccountName: {{ include "nlk.fullname" . }} + volumes: + - name: config + configMap: + name: nlk-config diff --git a/version b/version index b1e80bb..845639e 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.1.3 +0.1.4 From 300ea61c2417cd804ecfae00f0372b978f0e9388 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 26 Sep 2024 19:24:21 -0700 Subject: [PATCH 041/136] Drop configmap access for operator Once the operator reads from the configmap mounted as a volume, it does not need access to the k8s API to read the config map itself. --- charts/nlk/templates/clusterrole.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/nlk/templates/clusterrole.yaml b/charts/nlk/templates/clusterrole.yaml index 4164475..0ab90bb 100644 --- a/charts/nlk/templates/clusterrole.yaml +++ b/charts/nlk/templates/clusterrole.yaml @@ -7,7 +7,6 @@ rules: - apiGroups: - "" resources: - - configmaps - nodes - secrets - services From bbf990459445145f5dccb34fcc56e796eef5651d Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 27 Sep 2024 12:40:24 -0700 Subject: [PATCH 042/136] Grant permissions to read configmaps This is needed temporarily while nlk still needs to read the configmap and the code for swapping it out with a config file does not land. The order of merges that I am thinking: - Get this MR landed. - Land testenv changes to use helm. - Land code to read settings from configfile. - Remove configmap permissions. --- charts/nlk/templates/clusterrole.yaml | 1 + version | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/charts/nlk/templates/clusterrole.yaml b/charts/nlk/templates/clusterrole.yaml index 0ab90bb..4164475 100644 --- a/charts/nlk/templates/clusterrole.yaml +++ b/charts/nlk/templates/clusterrole.yaml @@ -7,6 +7,7 @@ rules: - apiGroups: - "" resources: + - configmaps - nodes - secrets - services diff --git a/version b/version index 845639e..9faa1b7 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.1.4 +0.1.5 From 1d8cf2d04fd537a8aeb8daa96d45af7ea91d5feb Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 5 Sep 2024 08:54:06 -0600 Subject: [PATCH 043/136] NLB-5342 Configuration settings now read from config file This means that we do not have to grant the operator app any permissions to access kubernetes secrets. Reading from the config file changes the application's behavior in that settings are no longer changed whenever a config map is updated at run time, as they used to be. Settings are now concurrency-safe, because they pass new values instead of a shared pointer to their consumers in separate goroutines. Settings also no longer pass the application context to consumers as this is a well-document go anti-pattern. Context should always be passed as a function parameter. Any operator modules that need access to a kubernetes client are constructed with a reference to the client, instead of gaining access to the client through settings. --- cmd/configuration-test-harness/doc.go | 1 - cmd/configuration-test-harness/main.go | 81 ---- cmd/nginx-loadbalancer-kubernetes/main.go | 46 ++- cmd/tls-config-factory-test-harness/main.go | 2 +- go.mod | 40 +- go.sum | 377 ++++-------------- internal/authentication/factory.go | 2 +- internal/authentication/factory_test.go | 21 +- internal/certification/certificates_test.go | 5 +- internal/communication/factory.go | 4 +- internal/communication/factory_test.go | 17 +- internal/communication/roundtripper_test.go | 21 +- internal/configuration/configuration_test.go | 52 +++ internal/configuration/settings.go | 277 ++----------- internal/configuration/testdata/test.yaml | 11 + internal/observation/handler.go | 6 +- internal/observation/handler_test.go | 21 +- internal/observation/watcher.go | 53 +-- internal/observation/watcher_test.go | 5 +- internal/synchronization/synchronizer.go | 4 +- internal/synchronization/synchronizer_test.go | 74 ++-- 21 files changed, 355 insertions(+), 765 deletions(-) delete mode 100644 cmd/configuration-test-harness/doc.go delete mode 100644 cmd/configuration-test-harness/main.go create mode 100644 internal/configuration/configuration_test.go create mode 100644 internal/configuration/testdata/test.yaml diff --git a/cmd/configuration-test-harness/doc.go b/cmd/configuration-test-harness/doc.go deleted file mode 100644 index 06ab7d0..0000000 --- a/cmd/configuration-test-harness/doc.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/cmd/configuration-test-harness/main.go b/cmd/configuration-test-harness/main.go deleted file mode 100644 index 5079a9d..0000000 --- a/cmd/configuration-test-harness/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "path/filepath" - - configuration2 "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/sirupsen/logrus" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" -) - -func main() { - logrus.SetLevel(logrus.DebugLevel) - err := run() - if err != nil { - logrus.Fatal(err) - } -} - -func run() error { - logrus.Info("configuration-test-harness::run") - - ctx := context.Background() - var err error - - k8sClient, err := buildKubernetesClient() - if err != nil { - return fmt.Errorf(`error building a Kubernetes client: %w`, err) - } - - configuration, err := configuration2.NewSettings(ctx, k8sClient) - if err != nil { - return fmt.Errorf(`error occurred creating configuration: %w`, err) - } - - err = configuration.Initialize() - if err != nil { - return fmt.Errorf(`error occurred initializing configuration: %w`, err) - } - - go configuration.Run() - - <-ctx.Done() - - return err -} - -func buildKubernetesClient() (*kubernetes.Clientset, error) { - logrus.Debug("Watcher::buildKubernetesClient") - - var kubeconfig *string - var k8sConfig *rest.Config - - k8sConfig, err := rest.InClusterConfig() - if errors.Is(err, rest.ErrNotInCluster) { - if home := homedir.HomeDir(); home != "" { - path := filepath.Join(home, ".kube", "config") - kubeconfig = &path - - k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) - if err != nil { - return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) - } - } else { - return nil, fmt.Errorf(`not running in a Cluster: %w`, err) - } - } else if err != nil { - return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) - } - - client, err := kubernetes.NewForConfig(k8sConfig) - if err != nil { - return nil, fmt.Errorf(`error occurred creating a client: %w`, err) - } - return client, nil -} diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 89f764f..f8473c3 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -35,17 +35,12 @@ func run() error { return fmt.Errorf(`error building a Kubernetes client: %w`, err) } - settings, err := configuration.NewSettings(ctx, k8sClient) + settings, err := configuration.Read("config.yaml", "/etc/nginxaas-operator") if err != nil { - return fmt.Errorf(`error occurred creating settings: %w`, err) + return fmt.Errorf(`error occurred accessing configuration: %w`, err) } - err = settings.Initialize() - if err != nil { - return fmt.Errorf(`error occurred initializing settings: %w`, err) - } - - go settings.Run() + setLogLevel(settings.LogLevel) synchronizerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) @@ -58,12 +53,12 @@ func run() error { handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue) - watcher, err := observation.NewWatcher(settings, handler) + watcher, err := observation.NewWatcher(settings, handler, k8sClient) if err != nil { return fmt.Errorf(`error occurred creating a watcher: %w`, err) } - err = watcher.Initialize() + err = watcher.Initialize(ctx) if err != nil { return fmt.Errorf(`error occurred initializing the watcher: %w`, err) } @@ -74,7 +69,7 @@ func run() error { probeServer := probation.NewHealthServer() probeServer.Start() - err = watcher.Watch() + err = watcher.Watch(ctx) if err != nil { return fmt.Errorf(`error occurred watching for events: %w`, err) } @@ -83,6 +78,35 @@ func run() error { return nil } +func setLogLevel(logLevel string) { + logrus.Debugf("Settings::setLogLevel: %s", logLevel) + switch logLevel { + case "panic": + logrus.SetLevel(logrus.PanicLevel) + + case "fatal": + logrus.SetLevel(logrus.FatalLevel) + + case "error": + logrus.SetLevel(logrus.ErrorLevel) + + case "warn": + logrus.SetLevel(logrus.WarnLevel) + + case "info": + logrus.SetLevel(logrus.InfoLevel) + + case "debug": + logrus.SetLevel(logrus.DebugLevel) + + case "trace": + logrus.SetLevel(logrus.TraceLevel) + + default: + logrus.SetLevel(logrus.WarnLevel) + } +} + func buildKubernetesClient() (*kubernetes.Clientset, error) { logrus.Debug("Watcher::buildKubernetesClient") k8sConfig, err := rest.InClusterConfig() diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index 51f1d1d..7b853e0 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -31,7 +31,7 @@ func main() { logrus.Infof("\n\n\t*** Building TLS config for <<< %s >>>\n\n", name) - tlsConfig, err := authentication.NewTLSConfig(&settings.Settings) + tlsConfig, err := authentication.NewTLSConfig(settings.Settings) if err != nil { panic(err) } diff --git a/go.mod b/go.mod index 118de5f..056c7e2 100644 --- a/go.mod +++ b/go.mod @@ -11,42 +11,60 @@ toolchain go1.21.4 require ( github.com/nginxinc/nginx-plus-go-client v1.2.2 github.com/sirupsen/logrus v1.9.0 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.80.1 // indirect diff --git a/go.sum b/go.sum index 46a175a..098adea 100644 --- a/go.sum +++ b/go.sum @@ -1,63 +1,26 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -69,86 +32,59 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -164,296 +100,157 @@ github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -463,12 +260,7 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= @@ -481,9 +273,6 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index 32add62..a51d7ab 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -18,7 +18,7 @@ import ( "github.com/sirupsen/logrus" ) -func NewTLSConfig(settings *configuration.Settings) (*tls.Config, error) { +func NewTLSConfig(settings configuration.Settings) (*tls.Config, error) { logrus.Debugf("authentication::NewTLSConfig Creating TLS config for mode: '%s'", settings.TLSMode) switch settings.TLSMode { diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go index e9015c0..6b5fcaf 100644 --- a/internal/authentication/factory_test.go +++ b/internal/authentication/factory_test.go @@ -20,9 +20,8 @@ const ( func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { t.Parallel() - settings := configuration.Settings{} - tlsConfig, err := NewTLSConfig(&settings) + tlsConfig, err := NewTLSConfig(configuration.Settings{}) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -50,7 +49,7 @@ func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { }, } - tlsConfig, err := NewTLSConfig(&settings) + tlsConfig, err := NewTLSConfig(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -84,7 +83,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { }, } - _, err := NewTLSConfig(&settings) + _, err := NewTLSConfig(settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -108,7 +107,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) }, } - _, err := NewTLSConfig(&settings) + _, err := NewTLSConfig(settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -133,7 +132,7 @@ func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { }, } - tlsConfig, err := NewTLSConfig(&settings) + tlsConfig, err := NewTLSConfig(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -168,7 +167,7 @@ func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { }, } - _, err := NewTLSConfig(&settings) + _, err := NewTLSConfig(settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -193,7 +192,7 @@ func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { }, } - _, err := NewTLSConfig(&settings) + _, err := NewTLSConfig(settings) if err == nil { t.Fatalf(`Expected an error`) } @@ -209,7 +208,7 @@ func TestTlsFactory_CaTlsMode(t *testing.T) { TLSMode: configuration.CertificateAuthorityTLS, } - tlsConfig, err := NewTLSConfig(&settings) + tlsConfig, err := NewTLSConfig(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -245,7 +244,7 @@ func TestTlsFactory_CaMtlsMode(t *testing.T) { }, } - tlsConfig, err := NewTLSConfig(&settings) + tlsConfig, err := NewTLSConfig(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -280,7 +279,7 @@ func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { }, } - _, err := NewTLSConfig(&settings) + _, err := NewTLSConfig(settings) if err == nil { t.Fatalf(`Expected an error`) } diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go index 89b21bf..901964a 100644 --- a/internal/certification/certificates_test.go +++ b/internal/certification/certificates_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -94,9 +95,7 @@ func TestCertificates_ExerciseHandlers(t *testing.T) { //nolint:govet,staticcheck go func() { err := certificates.Run() - if err != nil { - t.Fatalf("error running Certificates: %v", err) - } + assert.NoError(t, err, "expected no error running certificates") }() cache.WaitForCacheSync(ctx.Done(), certificates.informer.HasSynced) diff --git a/internal/communication/factory.go b/internal/communication/factory.go index 64ffe68..2a3c09a 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -19,7 +19,7 @@ import ( // NewHTTPClient is a factory method to create a new Http Client with a default configuration. // RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, // the Headers are configured for JSON. -func NewHTTPClient(settings *configuration.Settings) (*netHttp.Client, error) { +func NewHTTPClient(settings configuration.Settings) (*netHttp.Client, error) { headers := NewHeaders(settings.APIKey) tlsConfig := NewTLSConfig(settings) transport := NewTransport(tlsConfig) @@ -49,7 +49,7 @@ func NewHeaders(apiKey string) []string { // NewTLSConfig is a factory method to create a new basic Tls Config. // More attention should be given to the use of `InsecureSkipVerify: true`, as it is not recommended for production use. -func NewTLSConfig(settings *configuration.Settings) *tls.Config { +func NewTLSConfig(settings configuration.Settings) *tls.Config { tlsConfig, err := authentication.NewTLSConfig(settings) if err != nil { logrus.Warnf("Failed to create TLS config: %v", err) diff --git a/internal/communication/factory_test.go b/internal/communication/factory_test.go index 398bb6c..65f5e5b 100644 --- a/internal/communication/factory_test.go +++ b/internal/communication/factory_test.go @@ -6,21 +6,13 @@ package communication import ( - "context" "testing" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "k8s.io/client-go/kubernetes/fake" ) func TestNewHTTPClient(t *testing.T) { t.Parallel() - k8sClient := fake.NewSimpleClientset() - settings, err := configuration.NewSettings(context.Background(), k8sClient) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - client, err := NewHTTPClient(settings) + + client, err := NewHTTPClient(defaultSettings()) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -79,9 +71,8 @@ func TestNewHeadersWithNoAPIKey(t *testing.T) { func TestNewTransport(t *testing.T) { t.Parallel() - k8sClient := fake.NewSimpleClientset() - settings, _ := configuration.NewSettings(context.Background(), k8sClient) - config := NewTLSConfig(settings) + + config := NewTLSConfig(defaultSettings()) transport := NewTransport(config) if transport == nil { diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index abee455..9913600 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -7,20 +7,17 @@ package communication import ( "bytes" - "context" netHttp "net/http" "testing" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "k8s.io/client-go/kubernetes/fake" ) func TestNewRoundTripper(t *testing.T) { t.Parallel() - k8sClient := fake.NewSimpleClientset() - settings, _ := configuration.NewSettings(context.Background(), k8sClient) + headers := NewHeaders("fakeKey") - transport := NewTransport(NewTLSConfig(settings)) + transport := NewTransport(NewTLSConfig(defaultSettings())) roundTripper := NewRoundTripper(headers, transport) if roundTripper == nil { @@ -54,13 +51,9 @@ func TestNewRoundTripper(t *testing.T) { func TestRoundTripperRoundTrip(t *testing.T) { t.Parallel() - k8sClient := fake.NewSimpleClientset() - settings, err := configuration.NewSettings(context.Background(), k8sClient) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } + headers := NewHeaders("fakeKey") - transport := NewTransport(NewTLSConfig(settings)) + transport := NewTransport(NewTLSConfig(defaultSettings())) roundTripper := NewRoundTripper(headers, transport) request, err := NewRequest("GET", "http://example.com", nil) @@ -95,3 +88,9 @@ func NewRequest(method string, url string, body []byte) (*netHttp.Request, error return request, nil } + +func defaultSettings() configuration.Settings { + return configuration.Settings{ + TLSMode: configuration.NoTLS, + } +} diff --git a/internal/configuration/configuration_test.go b/internal/configuration/configuration_test.go new file mode 100644 index 0000000..9694989 --- /dev/null +++ b/internal/configuration/configuration_test.go @@ -0,0 +1,52 @@ +package configuration_test + +import ( + "testing" + "time" + + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + + "github.com/stretchr/testify/require" +) + +func TestConfiguration(t *testing.T) { + t.Parallel() + expectedSettings := configuration.Settings{ + LogLevel: "warn", + NginxPlusHosts: []string{"https://10.0.0.1:9000/api"}, + TLSMode: configuration.NoTLS, + Certificates: &certification.Certificates{ + CaCertificateSecretKey: "fakeCAKey", + ClientCertificateSecretKey: "fakeCertKey", + }, + Handler: configuration.HandlerSettings{ + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-handler", + }, + }, + Synchronizer: configuration.SynchronizerSettings{ + MaxMillisecondsJitter: 750, + MinMillisecondsJitter: 250, + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-synchronizer", + }, + }, + Watcher: configuration.WatcherSettings{ + ResyncPeriod: 0, + ServiceAnnotation: "fakeServiceMatch", + }, + } + + settings, err := configuration.Read("test", "./testdata") + require.NoError(t, err) + require.Equal(t, expectedSettings, settings) +} diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 400b311..91ff27c 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -6,21 +6,14 @@ package configuration import ( - "context" "encoding/base64" "fmt" - "os" - "strings" "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" + + "github.com/spf13/viper" ) const ( @@ -111,8 +104,8 @@ type SynchronizerSettings struct { // Settings contains the configuration values needed by the application. type Settings struct { - // Context is the context used to control the application. - Context context.Context + // LogLevel is the user-specified log level. Defaults to warn. + LogLevel string // NginxPlusHosts is a list of Nginx Plus hosts that will be used to update the Border Servers. NginxPlusHosts []string @@ -127,15 +120,6 @@ type Settings struct { // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. Certificates *certification.Certificates - // K8sClient is the Kubernetes client used to communicate with the Kubernetes API. - K8sClient kubernetes.Interface - - // informer is the SharedInformer used to watch for changes to the ConfigMap . - informer cache.SharedInformer - - // eventHandlerRegistration is the object used to track the event handlers with the SharedInformer. - eventHandlerRegistration cache.ResourceEventHandlerRegistration - // Handler contains the configuration values needed by the Handler. Handler HandlerSettings @@ -146,17 +130,41 @@ type Settings struct { Watcher WatcherSettings } -// NewSettings creates a new Settings object with default values. -func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings, error) { - // get base64 encoded version of raw api key set by user - apiKey := base64.StdEncoding.EncodeToString([]byte(os.Getenv("NGINXAAS_DATAPLANE_API_KEY"))) - - settings := &Settings{ - Context: ctx, - K8sClient: k8sClient, - TLSMode: NoTLS, - APIKey: apiKey, - Certificates: nil, +// Read parses all the config and returns the values +func Read(configName, configPath string) (s Settings, err error) { + v := viper.New() + v.SetConfigName(configName) + v.SetConfigType("yaml") + v.AddConfigPath(configPath) + if err = v.ReadInConfig(); err != nil { + return s, err + } + + if err = v.BindEnv("NGINXAAS_DATAPLANE_API_KEY"); err != nil { + return s, err + } + + tlsMode := NoTLS + if t, err := validateTLSMode(v.GetString("tls-mode")); err != nil { + logrus.Errorf("could not validate tls mode: %v", err) + } else { + tlsMode = t + } + + serviceAnnotation := DefaultServiceAnnotation + if sa := v.GetString(ServiceAnnotationMatchKey); sa != "" { + serviceAnnotation = sa + } + + return Settings{ + LogLevel: v.GetString("log-level"), + NginxPlusHosts: v.GetStringSlice("nginx-hosts"), + TLSMode: tlsMode, + APIKey: base64.StdEncoding.EncodeToString([]byte(v.GetString("NGINXAAS_DATAPLANE_API_KEY"))), + Certificates: &certification.Certificates{ + CaCertificateSecretKey: v.GetString("ca-certificate"), + ClientCertificateSecretKey: v.GetString("client-certificate"), + }, Handler: HandlerSettings{ RetryCount: 5, Threads: 1, @@ -179,216 +187,15 @@ func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings }, Watcher: WatcherSettings{ ResyncPeriod: 0, - ServiceAnnotation: DefaultServiceAnnotation, + ServiceAnnotation: serviceAnnotation, }, - } - - return settings, nil -} - -// Initialize initializes the Settings object. Sets up a SharedInformer to watch for changes to the ConfigMap. -// This method must be called before the Run method. -func (s *Settings) Initialize() error { - logrus.Info("Settings::Initialize") - - var err error - - certificates := certification.NewCertificates(s.Context, s.K8sClient) - - err = certificates.Initialize() - if err != nil { - return fmt.Errorf(`error occurred initializing certificates: %w`, err) - } - - s.Certificates = certificates - - go certificates.Run() //nolint:errcheck - - logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieving nlk-config ConfigMap") - configMap, err := s.K8sClient.CoreV1().ConfigMaps(ConfigMapsNamespace).Get( - s.Context, "nlk-config", metav1.GetOptions{}, - ) - if err != nil { - return err - } - - s.handleUpdateEvent(nil, configMap) - logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieved nlk-config ConfigMap") - - informer := s.buildInformer() - - s.informer = informer - - err = s.initializeEventListeners() - if err != nil { - return fmt.Errorf(`error occurred initializing event listeners: %w`, err) - } - - return nil -} - -// Run starts the SharedInformer and waits for the Context to be canceled. -func (s *Settings) Run() { - logrus.Debug("Settings::Run") - - defer utilruntime.HandleCrash() - - go s.informer.Run(s.Context.Done()) - - <-s.Context.Done() -} - -func (s *Settings) buildInformer() cache.SharedInformer { - options := informers.WithNamespace(ConfigMapsNamespace) - factory := informers.NewSharedInformerFactoryWithOptions(s.K8sClient, ResyncPeriod, options) - informer := factory.Core().V1().ConfigMaps().Informer() - - return informer -} - -func (s *Settings) initializeEventListeners() error { - logrus.Debug("Settings::initializeEventListeners") - - var err error - - handlers := cache.ResourceEventHandlerFuncs{ - AddFunc: s.handleAddEvent, - UpdateFunc: s.handleUpdateEvent, - DeleteFunc: s.handleDeleteEvent, - } - - s.eventHandlerRegistration, err = s.informer.AddEventHandler(handlers) - if err != nil { - return fmt.Errorf(`error occurred registering event handlers: %w`, err) - } - - return nil + }, nil } -func (s *Settings) handleAddEvent(obj interface{}) { - logrus.Debug("Settings::handleAddEvent") - - if _, yes := isOurConfig(obj); yes { - s.handleUpdateEvent(nil, obj) - } -} - -func (s *Settings) handleDeleteEvent(obj interface{}) { - logrus.Debug("Settings::handleDeleteEvent") - - if _, yes := isOurConfig(obj); yes { - s.updateHosts([]string{}) - } -} - -func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { - logrus.Debug("Settings::handleUpdateEvent") - - configMap, yes := isOurConfig(newValue) - if !yes { - return - } - - hosts, found := configMap.Data["nginx-hosts"] - if found { - newHosts := s.parseHosts(hosts) - s.updateHosts(newHosts) - } else { - logrus.Warnf("Settings::handleUpdateEvent: nginx-hosts key not found in ConfigMap") - } - - tlsMode, err := validateTLSMode(configMap) - if err != nil { - // NOTE: the TLSMode defaults to NoTLS on startup, or the last known good value if previously set. - logrus.Errorf( - "Error with configured TLS Mode. TLS Mode has NOT been changed. The current mode is: '%v'. Error: %v. ", - s.TLSMode, err, - ) - } else { - s.TLSMode = tlsMode - } - - caCertificateSecretKey, found := configMap.Data["ca-certificate"] - if found { - s.Certificates.CaCertificateSecretKey = caCertificateSecretKey - logrus.Debugf("Settings::handleUpdateEvent: ca-certificate: %s", s.Certificates.CaCertificateSecretKey) - } else { - s.Certificates.CaCertificateSecretKey = "" - logrus.Warnf("Settings::handleUpdateEvent: ca-certificate key not found in ConfigMap") - } - - clientCertificateSecretKey, found := configMap.Data["client-certificate"] - if found { - s.Certificates.ClientCertificateSecretKey = clientCertificateSecretKey - logrus.Debugf("Settings::handleUpdateEvent: client-certificate: %s", s.Certificates.ClientCertificateSecretKey) - } else { - s.Certificates.ClientCertificateSecretKey = "" - logrus.Warnf("Settings::handleUpdateEvent: client-certificate key not found in ConfigMap") - } - - if serviceAnnotation, found := configMap.Data[ServiceAnnotationMatchKey]; found { - s.Watcher.ServiceAnnotation = serviceAnnotation - } else { - s.Watcher.ServiceAnnotation = DefaultServiceAnnotation - } - logrus.Debugf("Settings::handleUpdateEvent: %s: %s", ServiceAnnotationMatchKey, s.Watcher.ServiceAnnotation) - - setLogLevel(configMap.Data["log-level"]) - - logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) -} - -func validateTLSMode(configMap *corev1.ConfigMap) (TLSMode, error) { - tlsConfigMode, tlsConfigModeFound := configMap.Data["tls-mode"] - if !tlsConfigModeFound { - return NoTLS, fmt.Errorf(`tls-mode key not found in ConfigMap`) - } - +func validateTLSMode(tlsConfigMode string) (TLSMode, error) { if tlsMode, tlsModeFound := TLSModeMap[tlsConfigMode]; tlsModeFound { return tlsMode, nil } return NoTLS, fmt.Errorf(`invalid tls-mode value: %s`, tlsConfigMode) } - -func (s *Settings) parseHosts(hosts string) []string { - return strings.Split(hosts, ",") -} - -func (s *Settings) updateHosts(hosts []string) { - s.NginxPlusHosts = hosts -} - -func isOurConfig(obj interface{}) (*corev1.ConfigMap, bool) { - configMap, ok := obj.(*corev1.ConfigMap) - return configMap, ok && configMap.Name == ConfigMapName && configMap.Namespace == ConfigMapsNamespace -} - -func setLogLevel(logLevel string) { - logrus.Debugf("Settings::setLogLevel: %s", logLevel) - switch logLevel { - case "panic": - logrus.SetLevel(logrus.PanicLevel) - - case "fatal": - logrus.SetLevel(logrus.FatalLevel) - - case "error": - logrus.SetLevel(logrus.ErrorLevel) - - case "warn": - logrus.SetLevel(logrus.WarnLevel) - - case "info": - logrus.SetLevel(logrus.InfoLevel) - - case "debug": - logrus.SetLevel(logrus.DebugLevel) - - case "trace": - logrus.SetLevel(logrus.TraceLevel) - - default: - logrus.SetLevel(logrus.WarnLevel) - } -} diff --git a/internal/configuration/testdata/test.yaml b/internal/configuration/testdata/test.yaml new file mode 100644 index 0000000..717dcdb --- /dev/null +++ b/internal/configuration/testdata/test.yaml @@ -0,0 +1,11 @@ +ca-certificate: fakeCAKey +client-certificate: fakeCertKey +log-level: warn +nginx-hosts: https://10.0.0.1:9000/api +tls-mode: no-tls +service-annotation-match: fakeServiceMatch +creationTimestamp: "2024-09-04T17:59:20Z" +name: nlk-config +namespace: nlk +resourceVersion: "5909" +uid: 66d49974-49d6-4ad8-8135-dcebda7b5c9e diff --git a/internal/observation/handler.go b/internal/observation/handler.go index 5584939..bd823f6 100644 --- a/internal/observation/handler.go +++ b/internal/observation/handler.go @@ -19,7 +19,6 @@ import ( // HandlerInterface is the interface for the event handler type HandlerInterface interface { - // AddRateLimitedEvent defines the interface for adding an event to the event queue AddRateLimitedEvent(event *core.Event) @@ -35,12 +34,11 @@ type HandlerInterface interface { // The translation process may result in multiple events being generated. This fan-out mainly supports the differences // in NGINX Plus API calls for creating/updating Upstreams and deleting Upstreams. type Handler struct { - // eventQueue is the queue used to store events eventQueue workqueue.RateLimitingInterface // settings is the configuration settings - settings *configuration.Settings + settings configuration.Settings // synchronizer is the synchronizer used to synchronize the internal representation with a Border Server synchronizer synchronization.Interface @@ -48,7 +46,7 @@ type Handler struct { // NewHandler creates a new event handler func NewHandler( - settings *configuration.Settings, + settings configuration.Settings, synchronizer synchronization.Interface, eventQueue workqueue.RateLimitingInterface, ) *Handler { diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go index 72b6d8f..ba4add1 100644 --- a/internal/observation/handler_test.go +++ b/internal/observation/handler_test.go @@ -6,23 +6,17 @@ package observation import ( - "context" - "fmt" "testing" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" v1 "k8s.io/api/core/v1" - "k8s.io/client-go/util/workqueue" ) func TestHandler_AddsEventToSynchronizer(t *testing.T) { t.Parallel() - _, _, synchronizer, handler, err := buildHandler() - if err != nil { - t.Errorf(`should have been no error, %v`, err) - } + synchronizer, handler := buildHandler() event := &core.Event{ Type: core.Created, @@ -47,19 +41,12 @@ func TestHandler_AddsEventToSynchronizer(t *testing.T) { } func buildHandler() ( - *configuration.Settings, - workqueue.RateLimitingInterface, - *mocks.MockSynchronizer, *Handler, error, + *mocks.MockSynchronizer, *Handler, ) { - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - return nil, nil, nil, nil, fmt.Errorf(`should have been no error, %v`, err) - } - eventQueue := &mocks.MockRateLimiter{} synchronizer := &mocks.MockSynchronizer{} - handler := NewHandler(settings, synchronizer, eventQueue) + handler := NewHandler(configuration.Settings{}, synchronizer, eventQueue) - return settings, eventQueue, synchronizer, handler, nil + return synchronizer, handler } diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index a07ef12..9cc9a16 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -6,6 +6,7 @@ package observation import ( + "context" "errors" "fmt" "time" @@ -17,6 +18,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" ) @@ -33,26 +35,31 @@ type Watcher struct { // informer is the informer used to watch for changes to Kubernetes resources informer cache.SharedIndexInformer + k8sClient kubernetes.Interface + // settings is the configuration settings - settings *configuration.Settings + settings configuration.Settings } // NewWatcher creates a new Watcher -func NewWatcher(settings *configuration.Settings, handler HandlerInterface) (*Watcher, error) { +func NewWatcher( + settings configuration.Settings, handler HandlerInterface, k8sClient kubernetes.Interface, +) (*Watcher, error) { return &Watcher{ - handler: handler, - settings: settings, + handler: handler, + settings: settings, + k8sClient: k8sClient, }, nil } // Initialize initializes the Watcher, must be called before Watch -func (w *Watcher) Initialize() error { +func (w *Watcher) Initialize(ctx context.Context) error { logrus.Debug("Watcher::Initialize") var err error w.informer = w.buildInformer() - err = w.initializeEventListeners() + err = w.initializeEventListeners(ctx) if err != nil { return fmt.Errorf(`initialization error: %w`, err) } @@ -62,7 +69,7 @@ func (w *Watcher) Initialize() error { // Watch starts the process of watching for changes to Kubernetes resources. // Initialize must be called before Watch. -func (w *Watcher) Watch() error { +func (w *Watcher) Watch(ctx context.Context) error { logrus.Debug("Watcher::Watch") if w.informer == nil { @@ -72,17 +79,17 @@ func (w *Watcher) Watch() error { defer utilruntime.HandleCrash() defer w.handler.ShutDown() - go w.informer.Run(w.settings.Context.Done()) + go w.informer.Run(ctx.Done()) if !cache.WaitForNamedCacheSync( w.settings.Handler.WorkQueueSettings.Name, - w.settings.Context.Done(), + ctx.Done(), w.informer.HasSynced, ) { return fmt.Errorf(`error occurred waiting for the cache to sync`) } - <-w.settings.Context.Done() + <-ctx.Done() return nil } @@ -98,7 +105,7 @@ func (w *Watcher) isDesiredService(service *v1.Service) bool { // buildEventHandlerForAdd creates a function that is used as an event handler // for the informer when Add events are raised. -func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { +func (w *Watcher) buildEventHandlerForAdd(ctx context.Context) func(interface{}) { logrus.Info("Watcher::buildEventHandlerForAdd") return func(obj interface{}) { service := obj.(*v1.Service) @@ -106,7 +113,7 @@ func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { return } - nodeIps, err := w.retrieveNodeIps() + nodeIps, err := w.retrieveNodeIps(ctx) if err != nil { logrus.Errorf(`error occurred retrieving node ips: %v`, err) return @@ -120,7 +127,7 @@ func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { // buildEventHandlerForDelete creates a function that is used as an event handler // for the informer when Delete events are raised. -func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { +func (w *Watcher) buildEventHandlerForDelete(ctx context.Context) func(interface{}) { logrus.Info("Watcher::buildEventHandlerForDelete") return func(obj interface{}) { service := obj.(*v1.Service) @@ -128,7 +135,7 @@ func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { return } - nodeIps, err := w.retrieveNodeIps() + nodeIps, err := w.retrieveNodeIps(ctx) if err != nil { logrus.Errorf(`error occurred retrieving node ips: %v`, err) return @@ -142,7 +149,7 @@ func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { // buildEventHandlerForUpdate creates a function that is used as an event handler // for the informer when Update events are raised. -func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { +func (w *Watcher) buildEventHandlerForUpdate(ctx context.Context) func(interface{}, interface{}) { logrus.Info("Watcher::buildEventHandlerForUpdate") return func(previous, updated interface{}) { // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API @@ -151,7 +158,7 @@ func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { return } - nodeIps, err := w.retrieveNodeIps() + nodeIps, err := w.retrieveNodeIps(ctx) if err != nil { logrus.Errorf(`error occurred retrieving node ips: %v`, err) return @@ -168,7 +175,7 @@ func (w *Watcher) buildInformer() cache.SharedIndexInformer { logrus.Debug("Watcher::buildInformer") factory := informers.NewSharedInformerFactoryWithOptions( - w.settings.K8sClient, w.settings.Watcher.ResyncPeriod, + w.k8sClient, w.settings.Watcher.ResyncPeriod, ) informer := factory.Core().V1().Services().Informer() @@ -176,14 +183,14 @@ func (w *Watcher) buildInformer() cache.SharedIndexInformer { } // initializeEventListeners initializes the event listeners for the informer. -func (w *Watcher) initializeEventListeners() error { +func (w *Watcher) initializeEventListeners(ctx context.Context) error { logrus.Debug("Watcher::initializeEventListeners") var err error handlers := cache.ResourceEventHandlerFuncs{ - AddFunc: w.buildEventHandlerForAdd(), - DeleteFunc: w.buildEventHandlerForDelete(), - UpdateFunc: w.buildEventHandlerForUpdate(), + AddFunc: w.buildEventHandlerForAdd(ctx), + DeleteFunc: w.buildEventHandlerForDelete(ctx), + UpdateFunc: w.buildEventHandlerForUpdate(ctx), } w.eventHandlerRegistration, err = w.informer.AddEventHandler(handlers) @@ -196,13 +203,13 @@ func (w *Watcher) initializeEventListeners() error { // notMasterNode retrieves the IP Addresses of the nodes in the cluster. Currently, the master node is excluded. This is // because the master node may or may not be a worker node and thus may not be able to route traffic. -func (w *Watcher) retrieveNodeIps() ([]string, error) { +func (w *Watcher) retrieveNodeIps(ctx context.Context) ([]string, error) { started := time.Now() logrus.Debug("Watcher::retrieveNodeIps") var nodeIps []string - nodes, err := w.settings.K8sClient.CoreV1().Nodes().List(w.settings.Context, metav1.ListOptions{}) + nodes, err := w.k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) if err != nil { logrus.Errorf(`error occurred retrieving the list of nodes: %v`, err) return nil, err diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index 2a6d94b..f8de849 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -17,15 +17,14 @@ import ( func TestWatcher_MustInitialize(t *testing.T) { t.Parallel() watcher, _ := buildWatcher() - if err := watcher.Watch(); err == nil { + if err := watcher.Watch(context.Background()); err == nil { t.Errorf("Expected error, got %s", err) } } func buildWatcher() (*Watcher, error) { k8sClient := &kubernetes.Clientset{} - settings, _ := configuration.NewSettings(context.Background(), k8sClient) handler := &mocks.MockHandler{} - return NewWatcher(settings, handler) + return NewWatcher(configuration.Settings{}, handler, k8sClient) } diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 5280726..eadfbd8 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -39,12 +39,12 @@ type Interface interface { // See application/border_client.go and application/application_constants.go for details. type Synchronizer struct { eventQueue workqueue.RateLimitingInterface - settings *configuration.Settings + settings configuration.Settings } // NewSynchronizer creates a new Synchronizer. func NewSynchronizer( - settings *configuration.Settings, + settings configuration.Settings, eventQueue workqueue.RateLimitingInterface, ) (*Synchronizer, error) { synchronizer := Synchronizer{ diff --git a/internal/synchronization/synchronizer_test.go b/internal/synchronization/synchronizer_test.go index 3634513..d1710b2 100644 --- a/internal/synchronization/synchronizer_test.go +++ b/internal/synchronization/synchronizer_test.go @@ -6,9 +6,9 @@ package synchronization import ( - "context" "fmt" "testing" + "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" @@ -17,14 +17,10 @@ import ( func TestSynchronizer_NewSynchronizer(t *testing.T) { t.Parallel() - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(settings, rateLimiter) + synchronizer, err := NewSynchronizer(configuration.Settings{}, rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -44,13 +40,10 @@ func TestSynchronizer_AddEventNoHosts(t *testing.T) { UpstreamName: "", UpstreamServers: nil, } - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } + rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(settings, rateLimiter) + synchronizer, err := NewSynchronizer(defaultSettings(), rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -72,14 +65,10 @@ func TestSynchronizer_AddEventOneHost(t *testing.T) { t.Parallel() const expectedEventCount = 1 events := buildEvents(1) - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - settings.NginxPlusHosts = []string{"https://localhost:8080"} + rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(settings, rateLimiter) + synchronizer, err := NewSynchronizer(defaultSettings("https://localhost:8080"), rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -99,18 +88,15 @@ func TestSynchronizer_AddEventManyHosts(t *testing.T) { t.Parallel() const expectedEventCount = 1 events := buildEvents(1) - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - settings.NginxPlusHosts = []string{ + hosts := []string{ "https://localhost:8080", "https://localhost:8081", "https://localhost:8082", } + rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(settings, rateLimiter) + synchronizer, err := NewSynchronizer(defaultSettings(hosts...), rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -130,13 +116,9 @@ func TestSynchronizer_AddEventsNoHosts(t *testing.T) { t.Parallel() const expectedEventCount = 0 events := buildEvents(4) - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(settings, rateLimiter) + synchronizer, err := NewSynchronizer(defaultSettings(), rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -158,14 +140,9 @@ func TestSynchronizer_AddEventsOneHost(t *testing.T) { t.Parallel() const expectedEventCount = 4 events := buildEvents(4) - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - settings.NginxPlusHosts = []string{"https://localhost:8080"} rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(settings, rateLimiter) + synchronizer, err := NewSynchronizer(defaultSettings("https://localhost:8080"), rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -186,18 +163,16 @@ func TestSynchronizer_AddEventsManyHosts(t *testing.T) { const eventCount = 4 events := buildEvents(eventCount) rateLimiter := &mocks.MockRateLimiter{} - settings, err := configuration.NewSettings(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - settings.NginxPlusHosts = []string{ + + hosts := []string{ "https://localhost:8080", "https://localhost:8081", "https://localhost:8082", } - expectedEventCount := eventCount * len(settings.NginxPlusHosts) - synchronizer, err := NewSynchronizer(settings, rateLimiter) + expectedEventCount := eventCount * len(hosts) + + synchronizer, err := NewSynchronizer(defaultSettings(hosts...), rateLimiter) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -226,3 +201,20 @@ func buildEvents(count int) core.ServerUpdateEvents { } return events } + +func defaultSettings(nginxHosts ...string) configuration.Settings { + return configuration.Settings{ + NginxPlusHosts: nginxHosts, + Synchronizer: configuration.SynchronizerSettings{ + MaxMillisecondsJitter: 750, + MinMillisecondsJitter: 250, + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-synchronizer", + }, + }, + } +} From 32cb7589da1309d3c61aec639f607de2c5fc65af Mon Sep 17 00:00:00 2001 From: Nathan Bird Date: Mon, 30 Sep 2024 15:19:01 -0400 Subject: [PATCH 044/136] Get rid of helm chart cruft --- charts/nlk/Chart.yaml | 2 +- charts/nlk/values.yaml | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index 1f986a3..bea170b 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -7,8 +7,8 @@ home: https://github.com/nginxinc/nginx-loadbalancer-kubernetes icon: https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/main/nlk-logo.svg keywords: - nginx +- nginxaas - loadbalancer -- ingress kubeVersion: '>= 1.22.0-0' maintainers: - name: "@ciroque" diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 11d082b..b32fb4c 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -42,22 +42,6 @@ nlk: type: ClusterIP port: 80 - ingress: - enabled: false - className: "" - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - resources: requests: cpu: 100m @@ -66,13 +50,6 @@ nlk: # cpu: 100m # memory: 128Mi - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - # Additional volumes on the output Deployment definition. volumes: [] # - name: foo From ece843f10bf63d4bd38935384d4ceb6bbcfe16f0 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 30 Sep 2024 18:29:09 -0700 Subject: [PATCH 045/136] NLB-5678: Template the namespace We should respect the user supplied release namespace in the helm chart instead of hardcoding it to `nlk`. --- charts/nlk/templates/clusterrolebinding.yaml | 2 +- charts/nlk/templates/nlk-configmap.yaml | 2 +- charts/nlk/templates/nlk-deployment.yaml | 2 +- charts/nlk/templates/nlk-secret.yaml | 2 +- charts/nlk/templates/nlk-serviceaccount.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/charts/nlk/templates/clusterrolebinding.yaml b/charts/nlk/templates/clusterrolebinding.yaml index 0ccd455..8503a24 100644 --- a/charts/nlk/templates/clusterrolebinding.yaml +++ b/charts/nlk/templates/clusterrolebinding.yaml @@ -6,7 +6,7 @@ metadata: subjects: - kind: ServiceAccount name: {{ include "nlk.fullname" . }} - namespace: nlk + namespace: {{ .Release.Namespace }} roleRef: kind: ClusterRole name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml index ab8977f..8cd4d66 100644 --- a/charts/nlk/templates/nlk-configmap.yaml +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: nlk-config - namespace: nlk + namespace: {{ .Release.Namespace }} data: config.yaml: | {{- if .Values.nlk.config.entries.hosts }} diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index 3b5348e..35de7f1 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "nlk.fullname" . }} - namespace: nlk + namespace: {{ .Release.Namespace }} labels: app: nlk spec: diff --git a/charts/nlk/templates/nlk-secret.yaml b/charts/nlk/templates/nlk-secret.yaml index ff7d7ff..cb96486 100644 --- a/charts/nlk/templates/nlk-secret.yaml +++ b/charts/nlk/templates/nlk-secret.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Secret metadata: name: {{ include "nlk.fullname" . }} - namespace: nlk + namespace: {{ .Release.Namespace }} annotations: kubernetes.io/service-account.name: {{ include "nlk.fullname" . }} type: kubernetes.io/service-account-token diff --git a/charts/nlk/templates/nlk-serviceaccount.yaml b/charts/nlk/templates/nlk-serviceaccount.yaml index 5bdca4f..d2cd8e4 100644 --- a/charts/nlk/templates/nlk-serviceaccount.yaml +++ b/charts/nlk/templates/nlk-serviceaccount.yaml @@ -3,5 +3,5 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "nlk.fullname" . }} - namespace: nlk + namespace: {{ .Release.Namespace }} {{- end }} From df989bf3f287e08f6c631915bdbd15bea7a8dc4b Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 27 Sep 2024 16:25:57 -0700 Subject: [PATCH 046/136] NLB-5666: Inject dataplane API key via Helm THe operator needs the dataplane API Key to authenticate with the deployment dataplane endpoint. This commit adds the abailitiy for a user to supply a key via Helm, create a secret on their behalf, and then mounts it into the pod. --- charts/nlk/templates/_helpers.tpl | 4 ++++ charts/nlk/templates/dataplaneApiKey.yaml | 8 ++++++++ charts/nlk/templates/nlk-deployment.yaml | 6 ++++++ charts/nlk/values.yaml | 2 ++ version | 2 +- 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 charts/nlk/templates/dataplaneApiKey.yaml diff --git a/charts/nlk/templates/_helpers.tpl b/charts/nlk/templates/_helpers.tpl index 17a6405..119c164 100644 --- a/charts/nlk/templates/_helpers.tpl +++ b/charts/nlk/templates/_helpers.tpl @@ -48,6 +48,10 @@ Create chart name and version as used by the chart label. {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} +{{- define "nlk.apikeyname" -}} +{{- printf "%s-nginxaas-api-key" (include "nlk.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + {{/* Common labels */}} diff --git a/charts/nlk/templates/dataplaneApiKey.yaml b/charts/nlk/templates/dataplaneApiKey.yaml new file mode 100644 index 0000000..11f76f8 --- /dev/null +++ b/charts/nlk/templates/dataplaneApiKey.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include nlk.apikeyname . }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + nginxaasApiKey: {{ .Values.nlk.dataplaneApiKey | toString | b64enc }} diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index 35de7f1..eb00b3a 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -45,6 +45,12 @@ spec: initialDelaySeconds: {{ .Values.nlk.readyStatus.initialDelaySeconds }} periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} {{- end }} + env: + - name: NGINXAAS_DATAPLANE_API_KEY + valueFrom: + secretKeyRef: + name: {{ include nlk.apikeyname . }} + key: nginxaasApiKey volumeMounts: - name: config mountPath: /etc/nginxaas-operator diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index b32fb4c..33889c3 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -79,6 +79,8 @@ nlk: ca-certificate: "" client-certificate: "" + dataplaneApiKey: "" + logLevel: "warn" containerPort: diff --git a/version b/version index 9faa1b7..0ea3a94 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.1.5 +0.2.0 From 1f84f62f3a39123c55506740e9d3a7ccd2e9cb81 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 1 Oct 2024 00:29:39 -0700 Subject: [PATCH 047/136] Cleanup helm chart values These are visible values that do not do anything on the chart itself for now. Cleaning these up and we can add them as we make progress on nlk. --- charts/nlk/Chart.yaml | 4 +--- charts/nlk/values.yaml | 47 +----------------------------------------- 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index bea170b..64f735a 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -1,10 +1,8 @@ --- apiVersion: v2 appVersion: 0.1.0 -description: NGINX LoadBalancer for Kubernetes +description: NGINXaaS LoadBalancer for Kubernetes name: nginxaas-operator -home: https://github.com/nginxinc/nginx-loadbalancer-kubernetes -icon: https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/main/nlk-logo.svg keywords: - nginx - nginxaas diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 33889c3..4e7e71d 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -24,55 +24,10 @@ nlk: # Annotations to add to the service account annotations: {} - podAnnotations: {} - podLabels: {} - - podSecurityContext: {} - # fsGroup: 2000 - - securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - - service: - type: ClusterIP - port: 80 - - resources: - requests: - cpu: 100m - memory: 128Mi - # limits: - # cpu: 100m - # memory: 128Mi - - # Additional volumes on the output Deployment definition. - volumes: [] - # - name: foo - # secret: - # secretName: mysecret - # optional: false - - # Additional volumeMounts on the output Deployment definition. - volumeMounts: [] - # - name: foo - # mountPath: "/etc/foo" - # readOnly: true - - nodeSelector: {} - - tolerations: [] - - affinity: {} - config: entries: hosts: - "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + "" defaultTLS: tls-mode: "no-tls" From e7e629a0c81f40b78ac976724dede48079aa4dbe Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 1 Oct 2024 11:24:40 -0700 Subject: [PATCH 048/136] Quote the helm function --- charts/nlk/templates/dataplaneApiKey.yaml | 2 +- charts/nlk/templates/nlk-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/nlk/templates/dataplaneApiKey.yaml b/charts/nlk/templates/dataplaneApiKey.yaml index 11f76f8..2051138 100644 --- a/charts/nlk/templates/dataplaneApiKey.yaml +++ b/charts/nlk/templates/dataplaneApiKey.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Secret metadata: - name: {{ include nlk.apikeyname . }} + name: {{ include "nlk.apikeyname" . }} namespace: {{ .Release.Namespace }} type: Opaque data: diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index eb00b3a..bc12f5c 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -49,7 +49,7 @@ spec: - name: NGINXAAS_DATAPLANE_API_KEY valueFrom: secretKeyRef: - name: {{ include nlk.apikeyname . }} + name: {{ include "nlk.apikeyname" . }} key: nginxaasApiKey volumeMounts: - name: config From 5b306543a3ca20954c30a25c71aa19123a5c53fb Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 1 Oct 2024 11:58:04 -0700 Subject: [PATCH 049/136] Default the API Key value in helm The default is set as not doing so causes helm to produce an empty secret cause the pod to fail the secret mount. This was missed on original testing as I did not realize that a user could skip specifying the secret as well. --- charts/nlk/values.yaml | 3 ++- version | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 4e7e71d..19713f5 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -34,7 +34,8 @@ nlk: ca-certificate: "" client-certificate: "" - dataplaneApiKey: "" + # Override with your own NGINXaaS dataplane API Key. + dataplaneApiKey: "test" logLevel: "warn" diff --git a/version b/version index 0ea3a94..0c62199 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.2.0 +0.2.1 From a6b8e3b5ba7f44fdea05a8875bed24ba9a1d757a Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 1 Oct 2024 18:41:15 -0700 Subject: [PATCH 050/136] Rename nginxaas-operator to nginxaas-loadbalancer-kubernetes --- .gitignore | 2 +- Dockerfile | 6 +++--- charts/nlk/Chart.yaml | 2 +- charts/nlk/templates/nlk-deployment.yaml | 2 +- charts/nlk/values.yaml | 4 ++-- cmd/nginx-loadbalancer-kubernetes/main.go | 2 +- scripts/build.sh | 2 +- scripts/docker.sh | 2 +- scripts/publish-helm.sh | 2 +- scripts/release.sh | 8 ++++---- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index f434c43..e69f538 100644 --- a/.gitignore +++ b/.gitignore @@ -88,4 +88,4 @@ results .go-build -nginxaas-operator-* +nginxaas-loadbalancer-kubernetes-* diff --git a/Dockerfile b/Dockerfile index d80281c..e53aef4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,6 @@ COPY docker-user /etc/passwd USER 101 COPY --from=base-certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -FROM base as nginxaas-operator -ENTRYPOINT ["/nginxaas-operator"] -COPY build/nginxaas-operator / +FROM base as nginxaas-loadbalancer-kubernetes +ENTRYPOINT ["/nginxaas-loadbalancer-kubernetes"] +COPY build/nginxaas-loadbalancer-kubernetes / diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index 64f735a..5c8d97c 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 appVersion: 0.1.0 description: NGINXaaS LoadBalancer for Kubernetes -name: nginxaas-operator +name: nginxaas-loadbalancer-kubernetes keywords: - nginx - nginxaas diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index bc12f5c..826e251 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -53,7 +53,7 @@ spec: key: nginxaasApiKey volumeMounts: - name: config - mountPath: /etc/nginxaas-operator + mountPath: /etc/nginxaas-loadbalancer-kubernetes serviceAccountName: {{ include "nlk.fullname" . }} volumes: - name: config diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 19713f5..3004231 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -1,5 +1,5 @@ nlk: - name: nginxaas-operator + name: nginxaas-loadbalancer-kubernetes kind: deployment @@ -7,7 +7,7 @@ nlk: image: registry: registry-1.docker.io - repository: nginx/nginxaas-operator + repository: nginx/nginxaas-loadbalancer-kubernetes pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. tag: release-0.1.0 diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index f8473c3..a9f6cd9 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -35,7 +35,7 @@ func run() error { return fmt.Errorf(`error building a Kubernetes client: %w`, err) } - settings, err := configuration.Read("config.yaml", "/etc/nginxaas-operator") + settings, err := configuration.Read("config.yaml", "/etc/nginxaas-loadbalancer-kubernetes") if err != nil { return fmt.Errorf(`error occurred accessing configuration: %w`, err) } diff --git a/scripts/build.sh b/scripts/build.sh index aafb418..fb1a634 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -36,5 +36,5 @@ ldflags=( go build \ -v -tags "release osusergo" \ -ldflags "${ldflags[*]}" \ - -o "${BUILD_DIR}/nginxaas-operator" \ + -o "${BUILD_DIR}/nginxaas-loadbalancer-kubernetes" \ "$pkg_path" diff --git a/scripts/docker.sh b/scripts/docker.sh index af473f1..6208b79 100755 --- a/scripts/docker.sh +++ b/scripts/docker.sh @@ -69,7 +69,7 @@ parse_args() { } # MAIN -image="nginxaas-operator" +image="nginxaas-loadbalancer-kubernetes" parse_args "$@" init_ci_vars diff --git a/scripts/publish-helm.sh b/scripts/publish-helm.sh index 8abecc5..71155d9 100755 --- a/scripts/publish-helm.sh +++ b/scripts/publish-helm.sh @@ -5,7 +5,7 @@ set -eo pipefail ROOT_DIR=$(git rev-parse --show-toplevel) publish_helm() { - pkg="nginxaas-operator-${version}.tgz" + pkg="nginxaas-loadbalancer-kubernetes-${version}.tgz" helm package --version "${version}" --app-version "${version}" charts/nlk helm push "${pkg}" "${repo}" } diff --git a/scripts/release.sh b/scripts/release.sh index aa942c8..6de2182 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -3,10 +3,10 @@ set -eo pipefail docker-image() { - SRC_PATH="nginx-azure-lb/nginxaas-operator/nginxaas-operator" + SRC_PATH="nginx-azure-lb/nginxaas-loadbalancer-kubernetes/nginxaas-loadbalancer-kubernetes" SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:main-${SRC_TAG}" - DST_PATH="nginx/nginxaas-operator" + DST_PATH="nginx/nginxaas-loadbalancer-kubernetes" DST_TAG="${CI_COMMIT_TAG}" DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" @@ -16,7 +16,7 @@ docker-image() { } helm-chart() { - SRC_PATH="nginx-azure-lb/nginxaas-operator/charts/main/nginxaas-operator" + SRC_PATH="nginx-azure-lbnginxaas-loadbalancer-kubernetes/charts/main/nginxaas-loadbalancer-kubernetes" SRC_TAG="0.1.0" SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}" DST_PATH="nginxcharts" @@ -24,7 +24,7 @@ helm-chart() { DST_CHART="oci://${DST_REGISTRY}/${DST_PATH}" helm pull "${SRC_CHART}" --version "${SRC_TAG}" - helm push nginxaas-operator-${DST_TAG}.tgz "${DST_CHART}" + helm push nginxaas-loadbalancer-kubernetes-${DST_TAG}.tgz "${DST_CHART}" } From d1b5186043b8e2885164ab3b2b33554c60ab9177 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 2 Oct 2024 13:09:10 -0700 Subject: [PATCH 051/136] Fix helm repo path --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 6de2182..730dfcb 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -16,7 +16,7 @@ docker-image() { } helm-chart() { - SRC_PATH="nginx-azure-lbnginxaas-loadbalancer-kubernetes/charts/main/nginxaas-loadbalancer-kubernetes" + SRC_PATH="nginx-azure-lb/nginxaas-loadbalancer-kubernetes/charts/main/nginxaas-loadbalancer-kubernetes" SRC_TAG="0.1.0" SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}" DST_PATH="nginxcharts" From 148dcc80ba597ef279ec26f9a058d309215a3084 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 2 Oct 2024 13:29:52 -0700 Subject: [PATCH 052/136] Version charts and images together We started doing this but I missed out fixing up the helm chart version in the release CI. In addition to versioning these together, I am also proposing that instead of cutting tags like `release-x.y.z`, we should use `v1.0.0`. --- scripts/release.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index 730dfcb..6ea4dd0 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -4,10 +4,8 @@ set -eo pipefail docker-image() { SRC_PATH="nginx-azure-lb/nginxaas-loadbalancer-kubernetes/nginxaas-loadbalancer-kubernetes" - SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "-") SRC_IMG="${SRC_REGISTRY}/${SRC_PATH}:main-${SRC_TAG}" DST_PATH="nginx/nginxaas-loadbalancer-kubernetes" - DST_TAG="${CI_COMMIT_TAG}" DST_IMG="${DST_REGISTRY}/${DST_PATH}:${DST_TAG}" docker pull "${SRC_IMG}" @@ -17,10 +15,8 @@ docker-image() { helm-chart() { SRC_PATH="nginx-azure-lb/nginxaas-loadbalancer-kubernetes/charts/main/nginxaas-loadbalancer-kubernetes" - SRC_TAG="0.1.0" SRC_CHART="oci://${SRC_REGISTRY}/${SRC_PATH}" DST_PATH="nginxcharts" - DST_TAG="0.1.0" DST_CHART="oci://${DST_REGISTRY}/${DST_PATH}" helm pull "${SRC_CHART}" --version "${SRC_TAG}" @@ -51,6 +47,8 @@ set_docker_common() { devops.docker.login # Login to Dockerhub. docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PASSWORD}" "${DST_REGISTRY}" + SRC_TAG=$(echo "${CI_COMMIT_TAG}" | cut -f 2 -d "v") + DST_TAG="${SRC_TAG}" } parse_args() { From 7250a55326b7dc04fd04830f8ff3885a63051124 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 2 Oct 2024 13:32:30 -0700 Subject: [PATCH 053/136] Fix up helm and docker versions This just brings them up to the latest. --- charts/nlk/Chart.yaml | 4 ++-- charts/nlk/values.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index 5c8d97c..f7f4c9c 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -1,6 +1,6 @@ --- apiVersion: v2 -appVersion: 0.1.0 +appVersion: 0.2.1 description: NGINXaaS LoadBalancer for Kubernetes name: nginxaas-loadbalancer-kubernetes keywords: @@ -14,4 +14,4 @@ maintainers: - name: "@abdennour" type: application -version: 0.1.0 +version: 0.2.1 diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 3004231..14a2b4f 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -10,7 +10,7 @@ nlk: repository: nginx/nginxaas-loadbalancer-kubernetes pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. - tag: release-0.1.0 + tag: 0.2.1 imagePullSecrets: [] nameOverride: "" From d56d38befe37a82793eaf9a872b4e9a90674ad17 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 2 Oct 2024 13:52:58 -0700 Subject: [PATCH 054/136] Fix up release regex to allow a release --- .gitlab-ci.yml | 2 +- scripts/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d3d94f4..dabd2b2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -183,7 +183,7 @@ checkmarx-scan: extends: - .devops-docker-cicd rules: - - if: '$CI_COMMIT_TAG =~ /^release-[\d]+\.[\d]+\.[\d]+/' + - if: '$CI_COMMIT_TAG =~ /^v[\d]+\.[\d]+\.[\d]+/' dockerhub-image-release: extends: diff --git a/scripts/release.sh b/scripts/release.sh index 6ea4dd0..f8ed556 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -72,7 +72,7 @@ main() { echo "This script is meant to be run in the CI." exit 1 fi - pttn="^release-[0-9]+\.[0-9]+\.[0-9]+" + pttn="^v[0-9]+\.[0-9]+\.[0-9]+" if ! [[ "${CI_COMMIT_TAG}" =~ $pttn ]]; then echo "CI_COMMIT_TAG needs to be set to valid semver format." exit 1 From ea9d9d19843968f80d95bfb181595d9f961789b2 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 1 Oct 2024 18:08:22 -0700 Subject: [PATCH 055/136] Fixup nlk configmap name We should really be templating the configmap name rather than hardcoding it (similar to what we did for secrets). --- charts/nlk/templates/_helpers.tpl | 6 +----- charts/nlk/templates/nlk-configmap.yaml | 2 +- charts/nlk/templates/nlk-deployment.yaml | 2 +- version | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/charts/nlk/templates/_helpers.tpl b/charts/nlk/templates/_helpers.tpl index 119c164..3f62fe9 100644 --- a/charts/nlk/templates/_helpers.tpl +++ b/charts/nlk/templates/_helpers.tpl @@ -80,11 +80,7 @@ app.kubernetes.io/instance: {{ .Release.Name }} Expand the name of the configmap. */}} {{- define "nlk.configName" -}} -{{- if .Values.nlk.customConfigMap -}} -{{ .Values.nlk.customConfigMap }} -{{- else -}} -{{- default (include "nlk.fullname" .) .Values.nlk.config.name -}} -{{- end -}} +{{- printf "%s-nlk-config" (include "nlk.fullname" .) | trunc 63 | trimSuffix "-" }} {{- end -}} {{/* diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml index 8cd4d66..78d4140 100644 --- a/charts/nlk/templates/nlk-configmap.yaml +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: nlk-config + name: {{ include "nlk.configName" . }} namespace: {{ .Release.Namespace }} data: config.yaml: | diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index 826e251..e49006f 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -58,4 +58,4 @@ spec: volumes: - name: config configMap: - name: nlk-config + name: {{ include "nlk.configName" . }} diff --git a/version b/version index 0c62199..ee1372d 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.2.1 +0.2.2 From 1b921702e7e26a087bb3dea6fee39bb9e0d3950e Mon Sep 17 00:00:00 2001 From: Nathan Bird Date: Tue, 1 Oct 2024 17:15:23 -0400 Subject: [PATCH 056/136] Remove configmap and secret permissions This is not a permission we need right now. If we merge back to NLK upstream we might want to _optionally_ restore this if the user needs to read certificate information. --- charts/nlk/Chart.yaml | 4 ++-- charts/nlk/templates/clusterrole.yaml | 2 -- charts/nlk/values.yaml | 2 +- version | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index f7f4c9c..a7e619a 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -1,6 +1,6 @@ --- apiVersion: v2 -appVersion: 0.2.1 +appVersion: 0.3.0 description: NGINXaaS LoadBalancer for Kubernetes name: nginxaas-loadbalancer-kubernetes keywords: @@ -14,4 +14,4 @@ maintainers: - name: "@abdennour" type: application -version: 0.2.1 +version: 0.3.0 diff --git a/charts/nlk/templates/clusterrole.yaml b/charts/nlk/templates/clusterrole.yaml index 4164475..c652c4c 100644 --- a/charts/nlk/templates/clusterrole.yaml +++ b/charts/nlk/templates/clusterrole.yaml @@ -7,9 +7,7 @@ rules: - apiGroups: - "" resources: - - configmaps - nodes - - secrets - services verbs: - get diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 14a2b4f..5fc1c0d 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -10,7 +10,7 @@ nlk: repository: nginx/nginxaas-loadbalancer-kubernetes pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. - tag: 0.2.1 + tag: 0.3.0 imagePullSecrets: [] nameOverride: "" diff --git a/version b/version index ee1372d..0d91a54 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.2.2 +0.3.0 From 7ac864bb6b74a930882934c27ff129eacf920353 Mon Sep 17 00:00:00 2001 From: Nathan Bird Date: Tue, 1 Oct 2024 17:18:34 -0400 Subject: [PATCH 057/136] Put config values in config key - group all the config vars together - make naming more consistent - update doc strings -- use ## to describe, # to show the key:value - remove references to non-supported ca certificate information - add nlk.config.serviceAnnotationMatch Documentation example ## trace,debug,info,warn,error,fatal,panic # logLevel: "warn" --- charts/nlk/Chart.yaml | 4 ++-- charts/nlk/templates/nlk-configmap.yaml | 15 +++++++----- charts/nlk/values.yaml | 32 +++++++++++++------------ version | 2 +- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index a7e619a..4e1860f 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -1,6 +1,6 @@ --- apiVersion: v2 -appVersion: 0.3.0 +appVersion: 0.4.0 description: NGINXaaS LoadBalancer for Kubernetes name: nginxaas-loadbalancer-kubernetes keywords: @@ -14,4 +14,4 @@ maintainers: - name: "@abdennour" type: application -version: 0.3.0 +version: 0.4.0 diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml index 78d4140..38475a9 100644 --- a/charts/nlk/templates/nlk-configmap.yaml +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -5,10 +5,13 @@ metadata: namespace: {{ .Release.Namespace }} data: config.yaml: | -{{- if .Values.nlk.config.entries.hosts }} - nginx-hosts: "{{ .Values.nlk.config.entries.hosts }}" +{{- with .Values.nlk.config.logLevel }} + log-level: "{{ . }}" +{{- end }} +{{- with .Values.nlk.config.nginxHosts }} + nginx-hosts: "{{ . }}" +{{- end }} + tls-mode: "{{ .Values.nlk.config.tls.mode }}" +{{- with .Values.nlk.config.serviceAnnotationMatch }} + service-annotation-match: "{{ . }}" {{- end }} - tls-mode: "{{ index .Values.nlk.defaultTLS "tls-mode" }}" - ca-certificate: "{{ index .Values.nlk.defaultTLS "ca-certificate" }}" - client-certificate: "{{ index .Values.nlk.defaultTLS "client-certificate" }}" - log-level: "{{ .Values.nlk.logLevel }}" diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 5fc1c0d..75f6b54 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -9,35 +9,37 @@ nlk: registry: registry-1.docker.io repository: nginx/nginxaas-loadbalancer-kubernetes pullPolicy: Always - # Overrides the image tag whose default is the chart appVersion. - tag: 0.3.0 + ## Overrides the image tag whose default is the chart appVersion. + tag: 0.4.0 imagePullSecrets: [] nameOverride: "" fullnameOverride: "" serviceAccount: - # Specifies whether a service account should be created + ## Specifies whether a service account should be created create: true - # Automatically mount a ServiceAccount's API credentials? + ## Automatically mount a ServiceAccount's API credentials? automount: true - # Annotations to add to the service account + ## Annotations to add to the service account annotations: {} config: - entries: - hosts: - "" + ## trace,debug,info,warn,error,fatal,panic + # logLevel: "warn" - defaultTLS: - tls-mode: "no-tls" - ca-certificate: "" - client-certificate: "" + ## the nginx hosts (comma-separated) to send upstream updates to + nginxHosts: "" - # Override with your own NGINXaaS dataplane API Key. - dataplaneApiKey: "test" + ## Sets the annotation value that NLK is looking for to watch a Service + # serviceAnnotationMatch: nginxaas + + tls: + ## can also be set to "no-tls" to disable server cert verification + mode: "ca-tls" - logLevel: "warn" + ## Override with your own NGINXaaS dataplane API Key. + dataplaneApiKey: "test" containerPort: http: 51031 diff --git a/version b/version index 0d91a54..1d0ba9e 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.3.0 +0.4.0 From 63c82a70f52fa9c7756d2d21133c287073b8931c Mon Sep 17 00:00:00 2001 From: sarna Date: Sun, 6 Oct 2024 22:57:51 -0700 Subject: [PATCH 058/136] Remove unused YAMLs We have officially transitions towards using Helm to install NLK. With that, we should also look at auto-generating these YAMLs via helm and not hardcode them into the repository. Removing them as they also add additional grep confusion while scanning through the repo. --- deployments/checks/sample-ingress-mod.yaml | 18 ---------- deployments/checks/sample-ingress.yaml | 18 ---------- deployments/deployment/configmap.yaml | 11 ------- deployments/deployment/deployment.yaml | 38 ---------------------- deployments/deployment/namespace.yaml | 6 ---- deployments/rbac/apply.sh | 12 ------- deployments/rbac/clusterrole.yaml | 10 ------ deployments/rbac/clusterrolebinding.yaml | 13 -------- deployments/rbac/secret.yaml | 8 ----- deployments/rbac/serviceaccount.yaml | 5 --- deployments/rbac/unapply.sh | 8 ----- 11 files changed, 147 deletions(-) delete mode 100644 deployments/checks/sample-ingress-mod.yaml delete mode 100644 deployments/checks/sample-ingress.yaml delete mode 100644 deployments/deployment/configmap.yaml delete mode 100644 deployments/deployment/deployment.yaml delete mode 100644 deployments/deployment/namespace.yaml delete mode 100755 deployments/rbac/apply.sh delete mode 100644 deployments/rbac/clusterrole.yaml delete mode 100644 deployments/rbac/clusterrolebinding.yaml delete mode 100644 deployments/rbac/secret.yaml delete mode 100644 deployments/rbac/serviceaccount.yaml delete mode 100755 deployments/rbac/unapply.sh diff --git a/deployments/checks/sample-ingress-mod.yaml b/deployments/checks/sample-ingress-mod.yaml deleted file mode 100644 index cd79305..0000000 --- a/deployments/checks/sample-ingress-mod.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: example-ingress - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$1 -spec: - rules: - - host: hello-world.net - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: web - port: - number: 8080 \ No newline at end of file diff --git a/deployments/checks/sample-ingress.yaml b/deployments/checks/sample-ingress.yaml deleted file mode 100644 index 7ff7fc5..0000000 --- a/deployments/checks/sample-ingress.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: example-ingress - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$1 -spec: - rules: - - host: hello-world.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: web - port: - number: 8080 \ No newline at end of file diff --git a/deployments/deployment/configmap.yaml b/deployments/deployment/configmap.yaml deleted file mode 100644 index fd30dbe..0000000 --- a/deployments/deployment/configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -data: - nginx-hosts: "https://10.0.0.1:9000/api" - tls-mode: "no-tls" - ca-certificate: "" - client-certificate: "" - log-level: "warn" -metadata: - name: nlk-config - namespace: nlk diff --git a/deployments/deployment/deployment.yaml b/deployments/deployment/deployment.yaml deleted file mode 100644 index 4c871c2..0000000 --- a/deployments/deployment/deployment.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nlk-deployment - namespace: nlk - labels: - app: nlk -spec: - replicas: 1 - selector: - matchLabels: - app: nlk - template: - metadata: - labels: - app: nlk - spec: - containers: - - name: nginx-loadbalancer-kubernetes - image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:latest - imagePullPolicy: Always - ports: - - name: http - containerPort: 51031 - protocol: TCP - livenessProbe: - httpGet: - path: /livez - port: 51031 - initialDelaySeconds: 5 - periodSeconds: 2 - readinessProbe: - httpGet: - path: /readyz - port: 51031 - initialDelaySeconds: 5 - periodSeconds: 2 - serviceAccountName: nginx-loadbalancer-kubernetes diff --git a/deployments/deployment/namespace.yaml b/deployments/deployment/namespace.yaml deleted file mode 100644 index 8f9e382..0000000 --- a/deployments/deployment/namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: nlk - labels: - name: nlk diff --git a/deployments/rbac/apply.sh b/deployments/rbac/apply.sh deleted file mode 100755 index 58248da..0000000 --- a/deployments/rbac/apply.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -pushd "$(dirname "$0")" - -echo "Applying all RBAC resources..." - -kubectl apply -f serviceaccount.yaml -kubectl apply -f clusterrole.yaml -kubectl apply -f clusterrolebinding.yaml -kubectl apply -f secret.yaml - -popd diff --git a/deployments/rbac/clusterrole.yaml b/deployments/rbac/clusterrole.yaml deleted file mode 100644 index c50bed8..0000000 --- a/deployments/rbac/clusterrole.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: resource-get-watch-list - namespace: nlk -rules: - - apiGroups: - - "" - resources: ["services", "nodes", "configmaps", "secrets"] - verbs: ["get", "watch", "list"] diff --git a/deployments/rbac/clusterrolebinding.yaml b/deployments/rbac/clusterrolebinding.yaml deleted file mode 100644 index d48ffb8..0000000 --- a/deployments/rbac/clusterrolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: "nginx-loadbalancer-kubernetes:resource-get-watch-list" - namespace: nlk -subjects: - - kind: ServiceAccount - name: nginx-loadbalancer-kubernetes - namespace: nlk -roleRef: - kind: ClusterRole - name: resource-get-watch-list - apiGroup: rbac.authorization.k8s.io diff --git a/deployments/rbac/secret.yaml b/deployments/rbac/secret.yaml deleted file mode 100644 index 71576bf..0000000 --- a/deployments/rbac/secret.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: nginx-loadbalancer-kubernetes-secret - namespace: nlk - annotations: - kubernetes.io/service-account.name: nginx-loadbalancer-kubernetes -type: kubernetes.io/service-account-token diff --git a/deployments/rbac/serviceaccount.yaml b/deployments/rbac/serviceaccount.yaml deleted file mode 100644 index 76f238c..0000000 --- a/deployments/rbac/serviceaccount.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: nginx-loadbalancer-kubernetes - namespace: nlk diff --git a/deployments/rbac/unapply.sh b/deployments/rbac/unapply.sh deleted file mode 100755 index f29f90d..0000000 --- a/deployments/rbac/unapply.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -echo "Unapplying all RBAC resources..." - -kubectl delete -f serviceaccount.yaml -kubectl delete -f clusterrole.yaml -kubectl delete -f clusterrolebinding.yaml -kubectl delete -f secret.yaml From 3e98257a8862ca2c48b868d6400c126eeec83ede Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 8 Oct 2024 15:08:01 -0600 Subject: [PATCH 059/136] NLB-5679 Added checksum field to helm nlk-deployment to force restart on configmap change --- charts/nlk/templates/nlk-deployment.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index e49006f..6270822 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -14,6 +14,8 @@ spec: metadata: labels: app: nlk + annotations: + checksum: {{ tpl (toYaml .Values.nlk) . | sha256sum }} spec: {{- with .Values.nlk.imagePullSecrets }} imagePullSecrets: From cbe6dfa4f30246db8b89f02f18b2531006200588 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 9 Oct 2024 10:34:39 -0600 Subject: [PATCH 060/136] NLB-5679 bumped to v0.4.1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 1d0ba9e..267577d 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.4.0 +0.4.1 From 332ea4fd2be24457a8fbfd0562ddfe2b8bf30012 Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 9 Oct 2024 10:25:26 -0700 Subject: [PATCH 061/136] NLB-5432: Add Gitlab Container scanning Gitlab uses Trivy underneath to scan an image which aligns with what we should try to use anyway. The nice thing about this work is: - Native integration with Gitlab. - Gitlab keeps Vulnerability DB up to date daily so we don't have to. --- .gitlab-ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dabd2b2..ab24a67 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,9 +12,11 @@ include: - project: "f5/nginx/tools/devops-utils" file: "/include/gitlab-sast.yml" ref: "master" + - template: Jobs/Container-Scanning.gitlab-ci.yml stages: - lint+test+build + - security scanning - release variables: @@ -177,6 +179,34 @@ checkmarx-scan: needs: [] allow_failure: true +# https://docs.gitlab.com/ee/user/application_security/container_scanning/ +container_scanning: + stage: security scanning + extends: + - .devops-docker-cicd + variables: + CS_DISABLE_LANGUAGE_VULNERABILITY_SCAN: "false" + GIT_STRATEGY: "fetch" + # CI_COMMIT_SHORT_SHA tag is being used to uniquely scan images being produced (or reproduced) on a feature branch. + CS_IMAGE: "${DEVOPS_DOCKER_URL_DEFAULT}/nginx-azure-lb/${CI_PROJECT_NAME}/nginxaas-loadbalancer-kubernetes:${CI_COMMIT_SHORT_SHA}" + # Gitlab container scanner needs the tenant ID to be set as it is using Azure auth libs underneath. + AZURE_TENANT_ID: ${ARM_TENANT_ID} + before_script: + # The Gitlab container scan image runs as a non-root user, which leads to the DevOps abstraction failing to bootstrap + # as the CI directories on the runner as seeded as "root". + - git config --global --add safe.directory "${CI_PROJECT_DIR}" + # Prerequisite toolchain to run the DevOps abstraction. + - sudo apt-get update && sudo apt-get install -y curl jq + - *import-devops-core-services + - devops.backend.docker.authenticate + - export CS_REGISTRY_USER="${DEVOPS_DOCKER_USER}" CS_REGISTRY_PASSWORD="${DEVOPS_DOCKER_PASS}" + rules: + - if: '$CI_COMMIT_TAG =~ /^v[\d]+\.[\d]+\.[\d]+/' + when: never + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + - when: on_success + .release-common: stage: release image: $DEVTOOLS_IMG From 7cbd48b942ff00448469bab2915b08ecb1920e65 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 10 Oct 2024 17:27:00 -0700 Subject: [PATCH 062/136] Add linting for helm chart --- .gitlab-ci.yml | 1 + Makefile | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ab24a67..d471ae7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,6 +89,7 @@ lint + unit-test + build: - *golang-private - | if [ "$CI_COMMIT_BRANCH" != "$CI_DEFAULT_BRANCH" ]; then + time make helm-lint time make lint git diff --exit-code time make test diff --git a/Makefile b/Makefile index f0c88a8..e53c440 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ DOCKER_TAG ?= latest GOPRIVATE = *.f5net.com,gitlab.com/f5 export GOPRIVATE -.PHONY: default tools deps fmt lint test build build.docker publish +.PHONY: default tools deps fmt lint test build build.docker publish helm-lint default: build @@ -35,6 +35,9 @@ lint: @find . -type f -name "*.go" -exec goimports -e -w {} \+ @golangci-lint run -v ./... +helm-lint: + helm lint charts/nlk/ + test: @./scripts/test.sh From 7c69d10bbb3f76cbd045162c8c076d21c8215979 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 7 Oct 2024 17:43:56 -0700 Subject: [PATCH 063/136] NLB-5717: Add manifest for marketplace app This is the manifest describing the app on Azure marketplace. --- charts/manifest.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 charts/manifest.yaml diff --git a/charts/manifest.yaml b/charts/manifest.yaml new file mode 100644 index 0000000..9138b81 --- /dev/null +++ b/charts/manifest.yaml @@ -0,0 +1,10 @@ +applicationName: "marketplace/nginxaas-loadbalancer-kubernetes" +publisher: "F5, Inc." +description: "A component that manages NGINXaaS for Azure deployment and makes it act as Load Balancer for kubernetes workloads." +version: 0.4.0 +helmChart: "./nlk" +clusterArmTemplate: "./armTemplate.json" +uiDefinition: "./createUIDefinition.json" +registryServer: "nlbmarketplaceacrprod.azurecr.io" +extensionRegistrationParameters: + defaultScope: "namespace" From afe5c006ecef0c5f2105bb4410316e8a366aa3e8 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 10 Oct 2024 17:46:43 -0700 Subject: [PATCH 064/136] NLB-5707: Create UI definition for AKS marketplace This file describes what the UX will look like for the NLK extension on the AKS marketplace. --- charts/createUIDefinition.json | 254 +++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 charts/createUIDefinition.json diff --git a/charts/createUIDefinition.json b/charts/createUIDefinition.json new file mode 100644 index 0000000..906e08e --- /dev/null +++ b/charts/createUIDefinition.json @@ -0,0 +1,254 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", + "handler": "Microsoft.Azure.CreateUIDef", + "version": "0.1.2-preview", + "parameters": { + "config": { + "isWizard": false, + "basics": { + "location": { + "visible": "[basics('createNewCluster')]", + "resourceTypes": ["Nginx.NginxPlus/nginxDeployments"] + }, + "resourceGroup": { + "allowExisting": true + } + } + }, + "basics": [ + { + "name": "createNewCluster", + "type": "Microsoft.Common.OptionsGroup", + "label": "Create new AKS cluster", + "defaultValue": "No", + "toolTip": "Create a new AKS cluster to install the extension.", + "constraints": { + "allowedValues": [ + { + "label": "Yes", + "value": true + }, + { + "label": "No", + "value": false + } + ], + "required": true + }, + "visible": true + } + ], + "steps": [ + { + "name": "clusterDetails", + "label": "Cluster Details", + "elements": [ + { + "name": "existingClusterSection", + "type": "Microsoft.Common.Section", + "elements": [ + { + "name": "clusterLookupControl", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(subscription().id, '/resourcegroups/', resourceGroup().name, '/providers/Microsoft.ContainerService/managedClusters?api-version=2022-03-01')]" + } + }, + { + "name": "existingClusterResourceName", + "type": "Microsoft.Common.DropDown", + "label": "AKS Cluster Name", + "toolTip": "The resource name of the existing AKS cluster.", + "constraints": { + "allowedValues": "[map(steps('clusterDetails').existingClusterSection.clusterLookupControl.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + } + } + ], + "visible": "[equals(basics('createNewCluster'), false)]" + }, + { + "name": "newClusterSection", + "type": "Microsoft.Common.Section", + "elements": [ + { + "name": "aksVersionLookupControl", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(subscription().id, '/providers/Microsoft.ContainerService/locations/', location(), '/orchestrators?api-version=2019-04-01&resource-type=managedClusters')]" + } + }, + { + "name": "newClusterResourceName", + "type": "Microsoft.Common.TextBox", + "label": "AKS cluster name", + "defaultValue": "", + "toolTip": "The resource name of the new AKS cluster. Use only allowed characters", + "constraints": { + "required": true, + "regex": "^[a-z0-9A-Z]{6,30}$", + "validationMessage": "Only alphanumeric characters are allowed, and the value must be 6-30 characters long." + } + }, + { + "name": "kubernetesVersion", + "type": "Microsoft.Common.DropDown", + "label": "Kubernetes version", + "toolTip": "The version of Kubernetes that should be used for this cluster. You will be able to upgrade this version after creating the cluster.", + "constraints": { + "allowedValues": "[map(steps('clusterDetails').newClusterSection.aksVersionLookupControl.properties.orchestrators, (item) => parse(concat('{\"label\":\"', item.orchestratorVersion, '\",\"value\":\"', item.orchestratorVersion, '\"}')))]", + "required": true + } + }, + { + "name": "vmSize", + "type": "Microsoft.Compute.SizeSelector", + "label": "VM size", + "toolTip": "The size of virtual machine of AKS worker nodes.", + "recommendedSizes": [ + "Standard_B4ms", + "Standard_DS2_v2", + "Standard_D4s_v3" + ], + "constraints": { + "allowedSizes": [ + "Standard_B4ms", + "Standard_DS2_v2", + "Standard_D4s_v3" + ], + "excludedSizes": [] + }, + "osPlatform": "Linux" + }, + { + "name": "osSKU", + "type": "Microsoft.Common.DropDown", + "label": "OS SKU", + "toolTip": "The SKU of Linux OS for VM.", + "defaultValue": "Ubuntu", + "constraints": { + "allowedValues": [ + { + "label": "Ubuntu", + "value": "Ubuntu" + }, + { + "label": "AzureLinux", + "value": "AzureLinux" + } + ], + "required": true + } + }, + { + "name": "enableAutoScaling", + "type": "Microsoft.Common.CheckBox", + "label": "Enable auto scaling", + "toolTip": "Enable auto scaling", + "defaultValue": true + }, + { + "name": "vmCount", + "type": "Microsoft.Common.Slider", + "min": 1, + "max": 10, + "label": "Number of AKS worker nodes", + "subLabel": "", + "defaultValue": 1, + "showStepMarkers": false, + "toolTip": "Specify the number of AKS worker nodes.", + "constraints": { + "required": false + }, + "visible": true + } + ], + "visible": "[basics('createNewCluster')]" + } + ] + }, + { + "name": "applicationDetails", + "label": "Application Details", + "elements": [ + { + "name": "extensionResourceName", + "type": "Microsoft.Common.TextBox", + "label": "Cluster extension resource name", + "defaultValue": "", + "toolTip": "Only lowercase alphanumeric characters are allowed, and the value must be 6-30 characters long.", + "constraints": { + "required": true, + "regex": "^[a-z0-9]{6,30}$", + "validationMessage": "Only lowercase alphanumeric characters are allowed, and the value must be 6-30 characters long." + }, + "visible": true + }, + { + "name": "extensionNamespace", + "type": "Microsoft.Common.TextBox", + "label": "Installation namespace", + "defaultValue": "nlk", + "toolTip": "Only lowercase alphanumeric characters are allowed, and the value must be 6-30 characters long.", + "constraints": { + "required": true, + "regex": "^[a-z0-9]{3,30}$", + "validationMessage": "Only lowercase alphanumeric characters are allowed, and the value must be 6-30 characters long." + }, + "visible": true + }, + { + "name": "extensionAutoUpgrade", + "type": "Microsoft.Common.CheckBox", + "label": "Allow minor version upgrades of extension", + "toolTip": "Allow exntension to be upgraded automatically to latest minor version.", + "visible": true + }, + { + "name": "nginxaasDataplaneApiKey", + "type": "Microsoft.Common.TextBox", + "label": "NGINXaaS Dataplane API Key", + "defaultValue": "", + "toolTip": "The Dataplane API Key for your NGINXaaS for Azure deployment.", + "visible": true + }, + { + "name": "nginxaasDataplaneApiEndpoint", + "type": "Microsoft.Common.TextBox", + "label": "NGINXaaS Dataplane API Endpoint", + "defaultValue": "", + "toolTip": "The Dataplane API Endpoint for your NGINXaaS for Azure deployment.", + "visible": true + }, + { + "name": "additionalProductInfo", + "type": "Microsoft.Common.InfoBox", + "visible": true, + "options": { + "icon": "Info", + "text": "Learn more about NGINXaaS for Azure.", + "uri": "https://docs.nginx.com/nginxaas/azure/" + } + } + ] + } + ], + "outputs": { + "location": "[location()]", + "createNewCluster": "[basics('createNewCluster')]", + "clusterResourceName": "[if(basics('createNewCluster'), steps('clusterDetails').newClusterSection.newClusterResourceName, steps('clusterDetails').existingClusterSection.existingClusterResourceName)]", + "kubernetesVersion": "[steps('clusterDetails').newClusterSection.kubernetesVersion]", + "vmSize": "[steps('clusterDetails').newClusterSection.vmSize]", + "osSKU": "[steps('clusterDetails').newClusterSection.osSKU]", + "vmEnableAutoScale": "[steps('clusterDetails').newClusterSection.enableAutoScaling]", + "vmCount": "[steps('clusterDetails').newClusterSection.vmCount]", + "extensionResourceName": "[steps('applicationDetails').extensionResourceName]", + "extensionAutoUpgrade": "[steps('applicationDetails').extensionAutoUpgrade]", + "extensionNamespace": "[steps('applicationDetails').extensionNamespace]", + "nginxaasDataplaneApiKey": "[steps('applicationDetails').nginxaasDataplaneApiKey]", + "nginxaasDataplaneApiEndpoint": "[steps('applicationDetails').nginxaasDataplaneApiEndpoint]" + } + } +} From c052f01415f80c3f7c6cf9d9c0181e2bc0aae051 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 10 Oct 2024 14:52:16 -0700 Subject: [PATCH 065/136] NLB-5733: Update Helm chart for Azure Marketplace Azure requires `global.azure.images` field in the Chart values so it can parse the Helm chart and copy them into an Azure-managed/owned ACR. This commit allows for non-Azure Marketplace users to continue to use the helm chart regularly and we will update the Chart values dynamically when we publish to markerplace to allow for newer workflows. --- charts/nlk/templates/_helpers.tpl | 8 +++++++- charts/nlk/templates/nlk-deployment.yaml | 3 +++ charts/nlk/values.yaml | 24 +++++++++++------------- version | 2 +- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/charts/nlk/templates/_helpers.tpl b/charts/nlk/templates/_helpers.tpl index 3f62fe9..6f5677d 100644 --- a/charts/nlk/templates/_helpers.tpl +++ b/charts/nlk/templates/_helpers.tpl @@ -91,14 +91,20 @@ Expand service account name. {{- end -}} {{- define "nlk.tag" -}} +{{- if .Values.global.azure -}} +{{- printf "%s" .Values.global.azure.images.nlk.tag -}} +{{- else -}} {{- default .Chart.AppVersion .Values.nlk.image.tag -}} {{- end -}} +{{- end -}} {{/* Expand image name. */}} {{- define "nlk.image" -}} -{{- if .Values.nlk.image.digest -}} +{{- if .Values.global.azure -}} +{{- printf "%s/%s:%s" .Values.global.azure.images.nlk.registry .Values.global.azure.images.nlk.repository (include "nlk.tag" .) -}} +{{- else if .Values.nlk.image.digest -}} {{- printf "%s/%s@%s" .Values.nlk.image.registry .Values.nlk.image.repository .Values.nlk.image.digest -}} {{- else -}} {{- printf "%s/%s:%s" .Values.nlk.image.registry .Values.nlk.image.repository (include "nlk.tag" .) -}} diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index 6270822..93fcd38 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -14,6 +14,9 @@ spec: metadata: labels: app: nlk +{{- if .Values.global.azure }} + azure-extensions-usage-release-identifier: {{ .Release.Name }} +{{- end }} annotations: checksum: {{ tpl (toYaml .Values.nlk) . | sha256sum }} spec: diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 75f6b54..4ce75ae 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -1,21 +1,27 @@ +##################################### +# Global Azure Marketplace configuration for AKS integration. +# DO NOT REMOVE +global: + azure: + # images: + # nlk: + # registry: registry-1.docker.io + # repository: nginx/nginxaas-loadbalancer-kubernetes + # tag: 0.4.0 +##################################### nlk: name: nginxaas-loadbalancer-kubernetes - kind: deployment - replicaCount: 1 - image: registry: registry-1.docker.io repository: nginx/nginxaas-loadbalancer-kubernetes pullPolicy: Always ## Overrides the image tag whose default is the chart appVersion. tag: 0.4.0 - imagePullSecrets: [] nameOverride: "" fullnameOverride: "" - serviceAccount: ## Specifies whether a service account should be created create: true @@ -23,39 +29,31 @@ nlk: automount: true ## Annotations to add to the service account annotations: {} - config: ## trace,debug,info,warn,error,fatal,panic # logLevel: "warn" ## the nginx hosts (comma-separated) to send upstream updates to nginxHosts: "" - ## Sets the annotation value that NLK is looking for to watch a Service # serviceAnnotationMatch: nginxaas - tls: ## can also be set to "no-tls" to disable server cert verification mode: "ca-tls" - ## Override with your own NGINXaaS dataplane API Key. dataplaneApiKey: "test" - containerPort: http: 51031 - liveStatus: enable: true port: 51031 initialDelaySeconds: 5 periodSeconds: 2 - readyStatus: enable: true port: 51031 initialDelaySeconds: 5 periodSeconds: 2 - rbac: ## Configures RBAC. create: true diff --git a/version b/version index 267577d..8f0916f 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.4.1 +0.5.0 From 1195350318abdf72725428b1b23164309cbed0e2 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 10 Oct 2024 14:52:16 -0700 Subject: [PATCH 066/136] NLB-5733: Update Helm chart for Azure Marketplace Azure requires `global.azure.images` field in the Chart values so it can parse the Helm chart and copy them into an Azure-managed/owned ACR. This commit allows for non-Azure Marketplace users to continue to use the helm chart regularly and we will update the Chart values dynamically when we publish to markerplace to allow for newer workflows. --- charts/nlk/templates/_helpers.tpl | 2 +- charts/nlk/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/nlk/templates/_helpers.tpl b/charts/nlk/templates/_helpers.tpl index 6f5677d..27dbe94 100644 --- a/charts/nlk/templates/_helpers.tpl +++ b/charts/nlk/templates/_helpers.tpl @@ -103,7 +103,7 @@ Expand image name. */}} {{- define "nlk.image" -}} {{- if .Values.global.azure -}} -{{- printf "%s/%s:%s" .Values.global.azure.images.nlk.registry .Values.global.azure.images.nlk.repository (include "nlk.tag" .) -}} +{{- printf "%s/%s:%s" .Values.global.azure.images.nlk.registry .Values.global.azure.images.nlk.image (include "nlk.tag" .) -}} {{- else if .Values.nlk.image.digest -}} {{- printf "%s/%s@%s" .Values.nlk.image.registry .Values.nlk.image.repository .Values.nlk.image.digest -}} {{- else -}} diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 4ce75ae..f5be244 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -6,7 +6,7 @@ global: # images: # nlk: # registry: registry-1.docker.io - # repository: nginx/nginxaas-loadbalancer-kubernetes + # image: nginx/nginxaas-loadbalancer-kubernetes # tag: 0.4.0 ##################################### nlk: From 60352fa3924372de35cb56afb58710a50fe92265 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 10 Oct 2024 17:47:50 -0700 Subject: [PATCH 067/136] NLB-5708: Create ARM template for AKS extension This defines the resources that will get created as part of the extension deployment. For the time being, Azure only allows creating AKS and extensions. --- charts/armTemplate.json | 256 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 charts/armTemplate.json diff --git a/charts/armTemplate.json b/charts/armTemplate.json new file mode 100644 index 0000000..1353fdb --- /dev/null +++ b/charts/armTemplate.json @@ -0,0 +1,256 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "extensionResourceName": { + "type": "string", + "metadata": { + "description": "The name of the extension." + } + }, + "extensionNamespace": { + "type": "string", + "defaultValue": "nlk" + }, + "clusterResourceName": { + "type": "String", + "metadata": { + "description": "The name of the Managed Cluster resource." + } + }, + "createNewCluster": { + "type": "Bool", + "defaultValue": true, + "metadata": { + "description": "When set to 'true', creates new AKS cluster. Otherwise, an existing cluster is used." + } + }, + "location": { + "type": "String", + "metadata": { + "description": "The location of AKS resource." + } + }, + "extensionAutoUpgrade": { + "defaultValue": false, + "metadata": { + "description": "Allow auto upgrade of minor version for the extension." + }, + "type": "bool" + }, + "nginxaasDataplaneApiKey": { + "type": "String" + }, + "nginxaasDataplaneApiEndpoint": { + "type": "String" + }, + "vmSize": { + "type": "String", + "defaultValue": "Standard_DS2_v2", + "metadata": { + "description": "VM size" + } + }, + "vmEnableAutoScale": { + "type": "Bool", + "defaultValue": true, + "metadata": { + "description": "enable auto scaling" + } + }, + "vmCount": { + "type": "Int", + "defaultValue": 3, + "metadata": { + "description": "VM count" + } + }, + "dnsPrefix": { + "defaultValue": "[concat(parameters('clusterResourceName'),'-dns')]", + "type": "String", + "metadata": { + "description": "Optional DNS prefix to use with hosted Kubernetes API server FQDN." + } + }, + "osDiskSizeGB": { + "defaultValue": 0, + "minValue": 0, + "maxValue": 1023, + "type": "Int", + "metadata": { + "description": "Disk size (in GiB) to provision for each of the agent pool nodes. This value ranges from 0 to 1023. Specifying 0 will apply the default disk size for that agentVMSize." + } + }, + "kubernetesVersion": { + "type": "String", + "defaultValue": "1.26.3", + "metadata": { + "description": "The version of Kubernetes." + } + }, + "networkPlugin": { + "defaultValue": "kubenet", + "allowedValues": [ + "azure", + "kubenet" + ], + "type": "String", + "metadata": { + "description": "Network plugin used for building Kubernetes network." + } + }, + "enableRBAC": { + "defaultValue": true, + "type": "Bool", + "metadata": { + "description": "Boolean flag to turn on and off of RBAC." + } + }, + "enablePrivateCluster": { + "defaultValue": false, + "type": "Bool", + "metadata": { + "description": "Enable private network access to the Kubernetes cluster." + } + }, + "enableHttpApplicationRouting": { + "defaultValue": true, + "type": "Bool", + "metadata": { + "description": "Boolean flag to turn on and off http application routing." + } + }, + "enableAzurePolicy": { + "defaultValue": false, + "type": "Bool", + "metadata": { + "description": "Boolean flag to turn on and off Azure Policy addon." + } + }, + "enableSecretStoreCSIDriver": { + "defaultValue": false, + "type": "Bool", + "metadata": { + "description": "Boolean flag to turn on and off secret store CSI driver." + } + }, + "osSKU": { + "type": "string", + "defaultValue": "AzureLinux", + "allowedValues": [ + "AzureLinux", + "Ubuntu" + ], + "metadata": { + "description": "The Linux SKU to use." + } + }, + "enableFIPS": { + "type": "Bool", + "defaultValue": true, + "metadata": { + "description": "Enable FIPS. https://learn.microsoft.com/en-us/azure/aks/create-node-pools#fips-enabled-node-pools" + } + } + }, + "variables": { + "plan-name": "DONOTMODIFY", + "plan-publisher": "DONOTMODIFY", + "plan-offerID": "DONOTMODIFY", + "releaseTrain": "DONOTMODIFY", + "clusterExtensionTypeName": "DONOTMODIFY" + }, + "resources": [ + { + "type": "Microsoft.ContainerService/managedClusters", + "condition": "[parameters('createNewCluster')]", + "apiVersion": "2022-11-01", + "name": "[parameters('clusterResourceName')]", + "location": "[parameters('location')]", + "dependsOn": [], + "tags": {}, + "sku": { + "name": "Basic", + "tier": "Free" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "enableRBAC": "[parameters('enableRBAC')]", + "dnsPrefix": "[parameters('dnsPrefix')]", + "agentPoolProfiles": [ + { + "name": "agentpool", + "osDiskSizeGB": "[parameters('osDiskSizeGB')]", + "count": "[parameters('vmCount')]", + "enableAutoScaling": "[parameters('vmEnableAutoScale')]", + "enableFIPS": "[parameters('enableFIPS')]", + "minCount": "[if(parameters('vmEnableAutoScale'), 1, json('null'))]", + "maxCount": "[if(parameters('vmEnableAutoScale'), 10, json('null'))]", + "vmSize": "[parameters('vmSize')]", + "osType": "Linux", + "osSKU": "[parameters('osSKU')]", + "storageProfile": "ManagedDisks", + "type": "VirtualMachineScaleSets", + "mode": "System", + "maxPods": 110, + "availabilityZones": [], + "enableNodePublicIP": false, + "tags": {} + } + ], + "networkProfile": { + "loadBalancerSku": "standard", + "networkPlugin": "[parameters('networkPlugin')]" + }, + "apiServerAccessProfile": { + "enablePrivateCluster": "[parameters('enablePrivateCluster')]" + }, + "addonProfiles": { + "httpApplicationRouting": { + "enabled": "[parameters('enableHttpApplicationRouting')]" + }, + "azurepolicy": { + "enabled": "[parameters('enableAzurePolicy')]" + }, + "azureKeyvaultSecretsProvider": { + "enabled": "[parameters('enableSecretStoreCSIDriver')]" + } + } + } + }, + { + "type": "Microsoft.KubernetesConfiguration/extensions", + "apiVersion": "2023-05-01", + "name": "[parameters('extensionResourceName')]", + "properties": { + "extensionType": "[variables('clusterExtensionTypeName')]", + "autoUpgradeMinorVersion": "[parameters('extensionAutoUpgrade')]", + "releaseTrain": "[variables('releaseTrain')]", + "configurationSettings": { + "nlk.config.dataplaneApiKey": "[parameters('nginxaasDataplaneApiKey')]", + "nlk.config.nginxHosts": "[parameters('nginxaasDataplaneApiEndpoint')]" + }, + "configurationProtectedSettings": {}, + "scope": { + "namespace": { + "targetNamespace": "[parameters('extensionNamespace')]" + } + } + }, + "plan": { + "name": "[variables('plan-name')]", + "publisher": "[variables('plan-publisher')]", + "product": "[variables('plan-offerID')]" + }, + "scope": "[concat('Microsoft.ContainerService/managedClusters/', parameters('clusterResourceName'))]", + "dependsOn": [ + "[resourceId('Microsoft.ContainerService/managedClusters/', parameters('clusterResourceName'))]" + ] + } + ], + "outputs": { + } +} From db737278b51ca46389370c5fcd8aa492f4f422a9 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 10 Oct 2024 19:22:06 -0700 Subject: [PATCH 068/136] Add CNAB validation We should make sure to validate the CNAB bundle as part of regular work, especially while we are iterating on NLK. --- .gitlab-ci.yml | 15 ++++++++++++ Makefile | 8 +++++++ scripts/cnab.sh | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100755 scripts/cnab.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d471ae7..71268a2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,6 +21,7 @@ stages: variables: DEVTOOLS_IMG: ${DEVOPS_DOCKER_URL_DEFAULT}/nginx-azure-lb/nlb-devtools:latest + CNAB_IMG: mcr.microsoft.com/container-package-app:latest workflow: rules: @@ -110,6 +111,20 @@ lint + unit-test + build: when: never - if: '$CI_COMMIT_BRANCH || $CI_MERGE_REQUEST_ID' +validate-cnab: + stage: lint+test+build + image: $CNAB_IMG + extends: + - .devops-docker-cicd-large + script: + - tdnf install -y make wget + - wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq + - make cnab-validate + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + - if: '$CI_COMMIT_BRANCH || $CI_MERGE_REQUEST_ID' + unit-test-data-race: variables: GO_DATA_RACE: "true" diff --git a/Makefile b/Makefile index e53c440..08e07ce 100644 --- a/Makefile +++ b/Makefile @@ -60,3 +60,11 @@ publish: build-linux build-linux-docker clean: rm -rf $(BUILD_DIR)/ + +.PHONY: cnab-validate cnab-package + +cnab-validate: + @./scripts/cnab.sh validate + +cnab-package: + @./scripts/cnab.sh package diff --git a/scripts/cnab.sh b/scripts/cnab.sh new file mode 100755 index 0000000..f8cc777 --- /dev/null +++ b/scripts/cnab.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +set -euo pipefail + +log() { + printf "\033[0;36m${*}\033[0m\n" >&2 +} + +package() { + log "Not Implemented." + exit 1 +} + +validate() { + CMD="cpa verify -d ${BUNDLE_DIR} --telemetryOptOut" + ${CMD} + +} + +set_version() { + VERSION=$(cat version) +} + +update_helm_chart() { + yq -ie '.global.azure.images.nlk.registry = .nlk.image.registry | .global.azure.images.nlk.image = .nlk.image.repository | .global.azure.images.nlk.tag = env(VERSION)' charts/nlk/values.yaml +} + +update_bundle() { + yq -ie '.version = env(VERSION)' charts/manifest.yaml +} + +check_ci() { + if [[ "$CI" != "true" ]]; then + log "This script should be the run in the CI only." + exit 1 + fi +} + +set_vars() { + BUNDLE_DIR="${CI_PROJECT_DIR}/charts/" +} + +main() { + check_ci + set_vars + set_version + update_helm_chart + update_bundle + local action="$1" + case "$action" in + validate) + validate + ;; + package) + package + ;; + *) + log "Action not supported." + exit 1 + ;; + esac +} + +main "$@" From 514fafaaa3a1a36c2813dac52d761f33827dab83 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 15 Oct 2024 12:59:55 -0700 Subject: [PATCH 069/136] Add constraints to dataplane-related user inputs contraints are required on textboxes. --- charts/createUIDefinition.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/charts/createUIDefinition.json b/charts/createUIDefinition.json index 906e08e..c7566d2 100644 --- a/charts/createUIDefinition.json +++ b/charts/createUIDefinition.json @@ -212,6 +212,11 @@ "label": "NGINXaaS Dataplane API Key", "defaultValue": "", "toolTip": "The Dataplane API Key for your NGINXaaS for Azure deployment.", + "constraints": { + "required": false, + "regex": ".*", + "validationMessage": "Use the dataplane API key for your deployment." + }, "visible": true }, { @@ -220,6 +225,11 @@ "label": "NGINXaaS Dataplane API Endpoint", "defaultValue": "", "toolTip": "The Dataplane API Endpoint for your NGINXaaS for Azure deployment.", + "constraints": { + "required": false, + "regex": ".*", + "validationMessage": "Retreive the dataplane API endpoint from your deployment." + }, "visible": true }, { From 0b18aa3f267cd91717b9f766d95e36d39f4473ca Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 15 Oct 2024 18:58:56 -0700 Subject: [PATCH 070/136] Allow releasing CNAB We need to release the CNAB package to our own ACR for the Azure-owned marketplace ACR to pick it up. This commit allows for us to publish the CNAB package as part of the release process this enabling continuous deployment to Azure Marketplace. --- .gitlab-ci.yml | 16 ++++++++++++++-- scripts/cnab.sh | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71268a2..8237868 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,7 @@ stages: - lint+test+build - security scanning - release + - release-cnab variables: DEVTOOLS_IMG: ${DEVOPS_DOCKER_URL_DEFAULT}/nginx-azure-lb/nlb-devtools:latest @@ -117,8 +118,8 @@ validate-cnab: extends: - .devops-docker-cicd-large script: - - tdnf install -y make wget - - wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq + - tdnf install -y make + - curl -L https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq - make cnab-validate rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' @@ -244,3 +245,14 @@ dockerhub-helm-release: - .release-common script: - ./scripts/release.sh helm-chart + +cnab-release: + extends: + - .devops-docker-cicd + - .release-common + stage: release-cnab + image: $CNAB_IMG + script: + - tdnf install -y make wget + - curl -L https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq + - make cnab-package diff --git a/scripts/cnab.sh b/scripts/cnab.sh index f8cc777..bac8a33 100755 --- a/scripts/cnab.sh +++ b/scripts/cnab.sh @@ -7,8 +7,9 @@ log() { } package() { - log "Not Implemented." - exit 1 + CMD="az acr login --name nlbmarketplaceacrprod --username ${ARM_CLIENT_ID_ACR} --password ${ARM_CLIENT_SECRET_ACR}" + CMD+=" && cpa buildbundle -d ${BUNDLE_DIR} --telemetryOptOut" + ${CMD} } validate() { From 6b787d3f022a37b23c0be158af95f94c4759efad Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 16 Oct 2024 14:19:28 -0700 Subject: [PATCH 071/136] Fix cnab publish commands Splitting up the publish commnds as bash is taking the cpa buildbundle command as an arg to az acr. --- scripts/cnab.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/cnab.sh b/scripts/cnab.sh index bac8a33..7d3c0b0 100755 --- a/scripts/cnab.sh +++ b/scripts/cnab.sh @@ -8,7 +8,8 @@ log() { package() { CMD="az acr login --name nlbmarketplaceacrprod --username ${ARM_CLIENT_ID_ACR} --password ${ARM_CLIENT_SECRET_ACR}" - CMD+=" && cpa buildbundle -d ${BUNDLE_DIR} --telemetryOptOut" + ${CMD} + CMD="cpa buildbundle -d ${BUNDLE_DIR} --telemetryOptOut" ${CMD} } From cfb92d980e55d0457ba004e9fcf9bfa9b1e12a04 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 17 Oct 2024 12:13:49 -0700 Subject: [PATCH 072/136] Change extension to ClusterScope When trying to install the extension in a namespaced mode (which is the default on the current offer/plan), the AKS extension machinery creates a k8s service account (different from the NLK service account) which is restricted to the extension namespace. Due to the aforementioned, the service account cannot read Cluster Roles that we create for NLK causing the extension to fail. Switching over to the cluster level extension will help here as when the AKS extension gets installed cluster-wide, the undelying service account has all the necessary permissions for the extension to be installed. --- charts/manifest.yaml | 3 ++- version | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/charts/manifest.yaml b/charts/manifest.yaml index 9138b81..6883ea9 100644 --- a/charts/manifest.yaml +++ b/charts/manifest.yaml @@ -7,4 +7,5 @@ clusterArmTemplate: "./armTemplate.json" uiDefinition: "./createUIDefinition.json" registryServer: "nlbmarketplaceacrprod.azurecr.io" extensionRegistrationParameters: - defaultScope: "namespace" + defaultScope: "cluster" + namespace: "nlk" diff --git a/version b/version index 8f0916f..4b9fcbe 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.0 +0.5.1 From 2da6ed5132caac1a55f796f8ce21f5615b627608 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 21 Oct 2024 10:14:45 -0700 Subject: [PATCH 073/136] Use release namespace everywhere This was missed as part of the manifest rollout. --- charts/armTemplate.json | 4 ++-- version | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/armTemplate.json b/charts/armTemplate.json index 1353fdb..ae3b699 100644 --- a/charts/armTemplate.json +++ b/charts/armTemplate.json @@ -235,8 +235,8 @@ }, "configurationProtectedSettings": {}, "scope": { - "namespace": { - "targetNamespace": "[parameters('extensionNamespace')]" + "cluster": { + "releaseNamespace": "[parameters('extensionNamespace')]" } } }, diff --git a/version b/version index 4b9fcbe..cb0c939 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.1 +0.5.2 From 15b19e450b35edb3655064fec0d24d037934e90f Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 21 Oct 2024 16:31:39 -0700 Subject: [PATCH 074/136] Fixup helm value supplied in extension It is `nlk.dataplaneApiKey` and not `nlk.config.dataplaneApiKey`. --- charts/armTemplate.json | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/armTemplate.json b/charts/armTemplate.json index ae3b699..510c784 100644 --- a/charts/armTemplate.json +++ b/charts/armTemplate.json @@ -230,7 +230,7 @@ "autoUpgradeMinorVersion": "[parameters('extensionAutoUpgrade')]", "releaseTrain": "[variables('releaseTrain')]", "configurationSettings": { - "nlk.config.dataplaneApiKey": "[parameters('nginxaasDataplaneApiKey')]", + "nlk.dataplaneApiKey": "[parameters('nginxaasDataplaneApiKey')]", "nlk.config.nginxHosts": "[parameters('nginxaasDataplaneApiEndpoint')]" }, "configurationProtectedSettings": {}, diff --git a/version b/version index cb0c939..be14282 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.2 +0.5.3 From 21c42c7b7431bad04f28b92cdd9c679528904ba3 Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 18 Oct 2024 15:52:36 -0700 Subject: [PATCH 075/136] Allow NLK to READ endpoint slices We will need to read endpoint slices in AKS to support the use-case for Cluster IP using Azure CNI. --- charts/nlk/templates/clusterrole.yaml | 8 ++++++++ version | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/charts/nlk/templates/clusterrole.yaml b/charts/nlk/templates/clusterrole.yaml index c652c4c..47f95dd 100644 --- a/charts/nlk/templates/clusterrole.yaml +++ b/charts/nlk/templates/clusterrole.yaml @@ -13,4 +13,12 @@ rules: - get - list - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch {{- end }} diff --git a/version b/version index be14282..a918a2a 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.3 +0.6.0 From b4ede3296be4cc179cb7fd832d9159d63662c64c Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 16 Oct 2024 15:15:03 -0600 Subject: [PATCH 076/136] NLB-5753 Added buildinfo package and injected vars at build time The code will then be able to reference build information, such as semver, easily at run time. --- pkg/buildinfo/buildinfo.go | 15 +++++++++++++++ scripts/build.sh | 4 +--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 pkg/buildinfo/buildinfo.go diff --git a/pkg/buildinfo/buildinfo.go b/pkg/buildinfo/buildinfo.go new file mode 100644 index 0000000..5d8839d --- /dev/null +++ b/pkg/buildinfo/buildinfo.go @@ -0,0 +1,15 @@ +package buildinfo + +var semVer string + +// SemVer is the version number of this build as provided by build pipeline +func SemVer() string { + return semVer +} + +var shortHash string + +// ShortHash is the 8 char git shorthash +func ShortHash() string { + return shortHash +} diff --git a/scripts/build.sh b/scripts/build.sh index fb1a634..c071a19 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -4,7 +4,6 @@ set -ex os="$1" -GIT_DESCRIBE=$(git describe --long --always --dirty) if [[ -z $CI_COMMIT_SHORT_SHA ]]; then CI_COMMIT_SHORT_SHA=$(git rev-parse --short=8 HEAD) fi @@ -21,13 +20,12 @@ fi mkdir -p "$BUILD_DIR" pkg_path="./cmd/nginx-loadbalancer-kubernetes" -BUILDPKG="gitlab.com/f5/nginx/nginxazurelb/nlk/pkg/buildinfo" +BUILDPKG="github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" ldflags=( # Set the value of the string variable in importpath named name to value. -X "'$BUILDPKG.semVer=$VERSION'" -X "'$BUILDPKG.shortHash=$CI_COMMIT_SHORT_SHA'" - -X "'$BUILDPKG.gitDescribe=$GIT_DESCRIBE'" -s # Omit the symbol table and debug information. -w # Omit the DWARF symbol table. -extldflags "'-fno-PIC'" From e2f0b06c2c96bfd15013a8c9b1be94b0c582a0c7 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 16 Oct 2024 15:16:14 -0600 Subject: [PATCH 077/136] NLB-5753 Replaced logrus with slog as the nlk logger; added version info to all log lines Go's slog now provides comparable functionality to logrus while being more up-to-date. Added version to all log lines. --- cmd/certificates-test-harness/main.go | 14 ++++--- cmd/nginx-loadbalancer-kubernetes/main.go | 46 ++++++++++----------- cmd/tls-config-factory-test-harness/main.go | 20 +++++---- go.mod | 1 - go.sum | 4 -- internal/application/border_client.go | 7 ++-- internal/application/null_border_client.go | 10 ++--- internal/authentication/factory.go | 18 ++++---- internal/certification/certificates.go | 28 ++++++------- internal/communication/factory.go | 4 +- internal/configuration/settings.go | 4 +- internal/observation/handler.go | 19 ++++----- internal/observation/watcher.go | 30 +++++++------- internal/probation/server.go | 16 ++++--- internal/probation/server_test.go | 4 +- internal/synchronization/synchronizer.go | 40 +++++++++--------- internal/translation/translator.go | 10 ++--- 17 files changed, 135 insertions(+), 140 deletions(-) diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go index 056d303..f3468a9 100644 --- a/cmd/certificates-test-harness/main.go +++ b/cmd/certificates-test-harness/main.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" + "os" "path/filepath" "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -15,15 +16,18 @@ import ( ) func main() { - logrus.SetLevel(logrus.DebugLevel) + handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}) + logger := slog.New(handler) + slog.SetDefault(logger) err := run() if err != nil { - logrus.Fatal(err) + slog.Error(err.Error()) + os.Exit(1) } } func run() error { - logrus.Info("certificates-test-harness::run") + slog.Info("certificates-test-harness::run") ctx := context.Background() var err error @@ -47,7 +51,7 @@ func run() error { } func buildKubernetesClient() (*kubernetes.Clientset, error) { - logrus.Debug("Watcher::buildKubernetesClient") + slog.Debug("Watcher::buildKubernetesClient") var kubeconfig *string var k8sConfig *rest.Config diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index a9f6cd9..8064348 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -8,12 +8,14 @@ package main import ( "context" "fmt" + "log/slog" + "os" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/observation" "github.com/nginxinc/kubernetes-nginx-ingress/internal/probation" "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" - "github.com/sirupsen/logrus" + "github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/util/workqueue" @@ -22,7 +24,8 @@ import ( func main() { err := run() if err != nil { - logrus.Fatal(err) + slog.Error(err.Error()) + os.Exit(1) } } @@ -40,7 +43,7 @@ func run() error { return fmt.Errorf(`error occurred accessing configuration: %w`, err) } - setLogLevel(settings.LogLevel) + initializeLogger(settings.LogLevel) synchronizerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) @@ -78,37 +81,30 @@ func run() error { return nil } -func setLogLevel(logLevel string) { - logrus.Debugf("Settings::setLogLevel: %s", logLevel) - switch logLevel { - case "panic": - logrus.SetLevel(logrus.PanicLevel) - - case "fatal": - logrus.SetLevel(logrus.FatalLevel) +func initializeLogger(logLevel string) { + programLevel := new(slog.LevelVar) + switch logLevel { case "error": - logrus.SetLevel(logrus.ErrorLevel) - + programLevel.Set(slog.LevelError) case "warn": - logrus.SetLevel(logrus.WarnLevel) - + programLevel.Set(slog.LevelWarn) case "info": - logrus.SetLevel(logrus.InfoLevel) - + programLevel.Set(slog.LevelInfo) case "debug": - logrus.SetLevel(logrus.DebugLevel) - - case "trace": - logrus.SetLevel(logrus.TraceLevel) - + programLevel.Set(slog.LevelDebug) default: - logrus.SetLevel(logrus.WarnLevel) + programLevel.Set(slog.LevelWarn) } + + handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) + logger := slog.New(handler).With("version", buildinfo.SemVer()) + slog.SetDefault(logger) + slog.Debug("Settings::setLogLevel", slog.String("level", logLevel)) } func buildKubernetesClient() (*kubernetes.Clientset, error) { - logrus.Debug("Watcher::buildKubernetesClient") + slog.Debug("Watcher::buildKubernetesClient") k8sConfig, err := rest.InClusterConfig() if err == rest.ErrNotInCluster { return nil, fmt.Errorf(`not running in a Cluster: %w`, err) @@ -125,7 +121,7 @@ func buildKubernetesClient() (*kubernetes.Clientset, error) { } func buildWorkQueue(settings configuration.WorkQueueSettings) workqueue.RateLimitingInterface { - logrus.Debug("Watcher::buildSynchronizerWorkQueue") + slog.Debug("Watcher::buildSynchronizerWorkQueue") rateLimiter := workqueue.NewItemExponentialFailureRateLimiter(settings.RateLimiterBase, settings.RateLimiterMax) return workqueue.NewNamedRateLimitingQueue(rateLimiter, settings.Name) diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index 7b853e0..7883d65 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -2,13 +2,13 @@ package main import ( "bufio" + "log/slog" "os" "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/sirupsen/logrus" ) const ( @@ -22,14 +22,16 @@ type TLSConfiguration struct { } func main() { - logrus.SetLevel(logrus.DebugLevel) + handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}) + logger := slog.New(handler) + slog.SetDefault(logger) configurations := buildConfigMap() for name, settings := range configurations { - logrus.Infof("\033[H\033[2J") + slog.Info("\033[H\033[2J") - logrus.Infof("\n\n\t*** Building TLS config for <<< %s >>>\n\n", name) + slog.Info("\n\n\t*** Building TLS config\n\n", "name", name) tlsConfig, err := authentication.NewTLSConfig(settings.Settings) if err != nil { @@ -47,15 +49,17 @@ func main() { certificateCount = len(tlsConfig.Certificates) } - logrus.Infof("Successfully built TLS config: \n\tDescription: %s \n\tRootCA count: %v\n\tCertificate count: %v", - settings.Description, rootCaCount, certificateCount, + slog.Info("Successfully built TLS config", + "description", settings.Description, + "rootCaCount", rootCaCount, + "certificateCount", certificateCount, ) _, _ = bufio.NewReader(os.Stdin).ReadBytes('\n') } - logrus.Infof("\033[H\033[2J") - logrus.Infof("\n\n\t*** All done! ***\n\n") + slog.Info("\033[H\033[2J") + slog.Info("\n\n\t*** All done! ***\n\n") } func buildConfigMap() map[string]TLSConfiguration { diff --git a/go.mod b/go.mod index 056c7e2..94a1319 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ toolchain go1.21.4 require ( github.com/nginxinc/nginx-plus-go-client v1.2.2 - github.com/sirupsen/logrus v1.9.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 k8s.io/api v0.26.0 diff --git a/go.sum b/go.sum index 098adea..1433805 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -134,7 +132,6 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -190,7 +187,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/application/border_client.go b/internal/application/border_client.go index a510953..e4eded8 100644 --- a/internal/application/border_client.go +++ b/internal/application/border_client.go @@ -7,9 +7,9 @@ package application import ( "fmt" + "log/slog" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/sirupsen/logrus" ) // Interface defines the functions required to implement a Border Client. @@ -19,8 +19,7 @@ type Interface interface { } // BorderClient defines any state need by the Border Client. -type BorderClient struct { -} +type BorderClient struct{} // NewBorderClient is the Factory function for creating a Border Client. // @@ -29,7 +28,7 @@ type BorderClient struct { // 2. Add a new constant in application_constants.go that acts as a key for selecting the client; // 3. Update the NewBorderClient factory method in border_client.go that returns the client; func NewBorderClient(clientType string, borderClient interface{}) (Interface, error) { - logrus.Debugf(`NewBorderClient for type: %s`, clientType) + slog.Debug("NewBorderClient", slog.String("client", clientType)) switch clientType { case ClientTypeNginxStream: diff --git a/internal/application/null_border_client.go b/internal/application/null_border_client.go index b59ca22..295ca62 100644 --- a/internal/application/null_border_client.go +++ b/internal/application/null_border_client.go @@ -6,15 +6,15 @@ package application import ( + "log/slog" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/sirupsen/logrus" ) // NullBorderClient is a BorderClient that does nothing. // It serves only to prevent a panic if the BorderClient // is not set correctly and errors from the factory methods are ignored. -type NullBorderClient struct { -} +type NullBorderClient struct{} // NewNullBorderClient is the Factory function for creating a NullBorderClient func NewNullBorderClient() (Interface, error) { @@ -23,12 +23,12 @@ func NewNullBorderClient() (Interface, error) { // Update logs a Warning. It is, after all, a NullObject Pattern implementation. func (nbc *NullBorderClient) Update(_ *core.ServerUpdateEvent) error { - logrus.Warn("NullBorderClient.Update called") + slog.Warn("NullBorderClient.Update called") return nil } // Delete logs a Warning. It is, after all, a NullObject Pattern implementation. func (nbc *NullBorderClient) Delete(_ *core.ServerUpdateEvent) error { - logrus.Warn("NullBorderClient.Delete called") + slog.Warn("NullBorderClient.Delete called") return nil } diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index a51d7ab..c0664f6 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -12,14 +12,14 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "log/slog" "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/sirupsen/logrus" ) func NewTLSConfig(settings configuration.Settings) (*tls.Config, error) { - logrus.Debugf("authentication::NewTLSConfig Creating TLS config for mode: '%s'", settings.TLSMode) + slog.Debug("authentication::NewTLSConfig Creating TLS config", "mode", settings.TLSMode) switch settings.TLSMode { case configuration.NoTLS: @@ -43,7 +43,7 @@ func NewTLSConfig(settings configuration.Settings) (*tls.Config, error) { } func buildSelfSignedTLSConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("authentication::buildSelfSignedTlsConfig Building self-signed TLS config") + slog.Debug("authentication::buildSelfSignedTlsConfig Building self-signed TLS config") certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err @@ -57,7 +57,7 @@ func buildSelfSignedTLSConfig(certificates *certification.Certificates) (*tls.Co } func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config") + slog.Debug("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config") certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err @@ -67,7 +67,7 @@ func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.C if err != nil { return nil, err } - logrus.Debugf("buildSelfSignedMtlsConfig Certificate: %v", certificate) + slog.Debug("buildSelfSignedMtlsConfig Certificate", "certificate", certificate) //nolint:gosec return &tls.Config{ @@ -79,14 +79,14 @@ func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.C } func buildBasicTLSConfig(skipVerify bool) *tls.Config { - logrus.Debugf("authentication::buildBasicTLSConfig skipVerify(%v)", skipVerify) + slog.Debug("authentication::buildBasicTLSConfig", slog.Bool("skipVerify", skipVerify)) return &tls.Config{ InsecureSkipVerify: skipVerify, //nolint:gosec } } func buildCATLSConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("authentication::buildCATLSConfig") + slog.Debug("authentication::buildCATLSConfig") certificate, err := buildCertificates(certificates.GetClientCertificate()) if err != nil { return nil, err @@ -100,12 +100,12 @@ func buildCATLSConfig(certificates *certification.Certificates) (*tls.Config, er } func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { - logrus.Debug("authentication::buildCertificates") + slog.Debug("authentication::buildCertificates") return tls.X509KeyPair(certificatePEM, privateKeyPEM) } func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { - logrus.Debug("authentication::buildCaCertificatePool") + slog.Debug("authentication::buildCaCertificatePool") block, _ := pem.Decode(caCert) if block == nil { return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index 3ecbc46..c59eb0e 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -12,8 +12,8 @@ package certification import ( "context" "fmt" + "log/slog" - "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -81,7 +81,7 @@ func (c *Certificates) GetClientCertificate() (core.SecretBytes, core.SecretByte // Initialize initializes the Certificates object. Sets up a SharedInformer for the Secrets Resource. func (c *Certificates) Initialize() error { - logrus.Info("Certificates::Initialize") + slog.Info("Certificates::Initialize") var err error @@ -101,7 +101,7 @@ func (c *Certificates) Initialize() error { // Run starts the SharedInformer. func (c *Certificates) Run() error { - logrus.Info("Certificates::Run") + slog.Info("Certificates::Run") if c.informer == nil { return fmt.Errorf(`initialize must be called before Run`) @@ -115,7 +115,7 @@ func (c *Certificates) Run() error { } func (c *Certificates) buildInformer() cache.SharedInformer { - logrus.Debug("Certificates::buildInformer") + slog.Debug("Certificates::buildInformer") options := informers.WithNamespace(SecretsNamespace) factory := informers.NewSharedInformerFactoryWithOptions(c.k8sClient, 0, options) @@ -125,7 +125,7 @@ func (c *Certificates) buildInformer() cache.SharedInformer { } func (c *Certificates) initializeEventHandlers() error { - logrus.Debug("Certificates::initializeEventHandlers") + slog.Debug("Certificates::initializeEventHandlers") var err error @@ -144,11 +144,11 @@ func (c *Certificates) initializeEventHandlers() error { } func (c *Certificates) handleAddEvent(obj interface{}) { - logrus.Debug("Certificates::handleAddEvent") + slog.Debug("Certificates::handleAddEvent") secret, ok := obj.(*corev1.Secret) if !ok { - logrus.Errorf("Certificates::handleAddEvent: unable to cast object to Secret") + slog.Error("Certificates::handleAddEvent: unable to cast object to Secret") return } @@ -162,15 +162,15 @@ func (c *Certificates) handleAddEvent(obj interface{}) { c.Certificates[secret.Name][k] = core.SecretBytes(v) } - logrus.Debugf("Certificates::handleAddEvent: certificates (%d)", len(c.Certificates)) + slog.Debug("Certificates::handleAddEvent", slog.Int("certCount", len(c.Certificates))) } func (c *Certificates) handleDeleteEvent(obj interface{}) { - logrus.Debug("Certificates::handleDeleteEvent") + slog.Debug("Certificates::handleDeleteEvent") secret, ok := obj.(*corev1.Secret) if !ok { - logrus.Errorf("Certificates::handleDeleteEvent: unable to cast object to Secret") + slog.Error("Certificates::handleDeleteEvent: unable to cast object to Secret") return } @@ -178,15 +178,15 @@ func (c *Certificates) handleDeleteEvent(obj interface{}) { delete(c.Certificates, secret.Name) } - logrus.Debugf("Certificates::handleDeleteEvent: certificates (%d)", len(c.Certificates)) + slog.Debug("Certificates::handleDeleteEvent", slog.Int("certCount", len(c.Certificates))) } func (c *Certificates) handleUpdateEvent(_ interface{}, newValue interface{}) { - logrus.Debug("Certificates::handleUpdateEvent") + slog.Debug("Certificates::handleUpdateEvent") secret, ok := newValue.(*corev1.Secret) if !ok { - logrus.Errorf("Certificates::handleUpdateEvent: unable to cast object to Secret") + slog.Error("Certificates::handleUpdateEvent: unable to cast object to Secret") return } @@ -194,5 +194,5 @@ func (c *Certificates) handleUpdateEvent(_ interface{}, newValue interface{}) { c.Certificates[secret.Name][k] = v } - logrus.Debugf("Certificates::handleUpdateEvent: certificates (%d)", len(c.Certificates)) + slog.Debug("Certificates::handleUpdateEvent", slog.Int("certCount", len(c.Certificates))) } diff --git a/internal/communication/factory.go b/internal/communication/factory.go index 2a3c09a..8664e9d 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -8,12 +8,12 @@ package communication import ( "crypto/tls" "fmt" + "log/slog" netHttp "net/http" "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/sirupsen/logrus" ) // NewHTTPClient is a factory method to create a new Http Client with a default configuration. @@ -52,7 +52,7 @@ func NewHeaders(apiKey string) []string { func NewTLSConfig(settings configuration.Settings) *tls.Config { tlsConfig, err := authentication.NewTLSConfig(settings) if err != nil { - logrus.Warnf("Failed to create TLS config: %v", err) + slog.Warn("Failed to create TLS config", "error", err) return &tls.Config{InsecureSkipVerify: true} //nolint:gosec } diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 91ff27c..ebeacc4 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -8,10 +8,10 @@ package configuration import ( "encoding/base64" "fmt" + "log/slog" "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -146,7 +146,7 @@ func Read(configName, configPath string) (s Settings, err error) { tlsMode := NoTLS if t, err := validateTLSMode(v.GetString("tls-mode")); err != nil { - logrus.Errorf("could not validate tls mode: %v", err) + slog.Error("could not validate tls mode", "error", err) } else { tlsMode = t } diff --git a/internal/observation/handler.go b/internal/observation/handler.go index bd823f6..9f44359 100644 --- a/internal/observation/handler.go +++ b/internal/observation/handler.go @@ -7,12 +7,12 @@ package observation import ( "fmt" + "log/slog" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" "github.com/nginxinc/kubernetes-nginx-ingress/internal/translation" - "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/workqueue" ) @@ -59,13 +59,13 @@ func NewHandler( // AddRateLimitedEvent adds an event to the event queue func (h *Handler) AddRateLimitedEvent(event *core.Event) { - logrus.Debugf(`Handler::AddRateLimitedEvent: %#v`, event) + slog.Debug(`Handler::AddRateLimitedEvent`, "event", event) h.eventQueue.AddRateLimited(event) } // Run starts the event handler, spins up Goroutines to process events, and waits for a stop signal func (h *Handler) Run(stopCh <-chan struct{}) { - logrus.Debug("Handler::Run") + slog.Debug("Handler::Run") for i := 0; i < h.settings.Handler.Threads; i++ { go wait.Until(h.worker, 0, stopCh) @@ -76,13 +76,13 @@ func (h *Handler) Run(stopCh <-chan struct{}) { // ShutDown stops the event handler and shuts down the event queue func (h *Handler) ShutDown() { - logrus.Debug("Handler::ShutDown") + slog.Debug("Handler::ShutDown") h.eventQueue.ShutDown() } // handleEvent feeds translated events to the synchronizer func (h *Handler) handleEvent(e *core.Event) error { - logrus.Debugf(`Handler::handleEvent: %#v`, e) + slog.Debug("Handler::handleEvent", "event", e) // TODO: Add Telemetry events, err := translation.Translate(e) @@ -97,9 +97,8 @@ func (h *Handler) handleEvent(e *core.Event) error { // handleNextEvent pulls an event from the event queue and feeds it to the event handler with retry logic func (h *Handler) handleNextEvent() bool { - logrus.Debug("Handler::handleNextEvent") evt, quit := h.eventQueue.Get() - logrus.Debugf(`Handler::handleNextEvent: %#v, quit: %v`, evt, quit) + slog.Debug("Handler::handleNextEvent", "event", evt, "quit", quit) if quit { return false } @@ -121,15 +120,15 @@ func (h *Handler) worker() { // withRetry handles errors from the event handler and requeues events that fail func (h *Handler) withRetry(err error, event *core.Event) { - logrus.Debug("Handler::withRetry") + slog.Debug("Handler::withRetry") if err != nil { // TODO: Add Telemetry if h.eventQueue.NumRequeues(event) < h.settings.Handler.RetryCount { h.eventQueue.AddRateLimited(event) - logrus.Infof(`Handler::withRetry: requeued event: %#v; error: %v`, event, err) + slog.Info("Handler::withRetry: requeued event", "event", event, "error", err) } else { h.eventQueue.Forget(event) - logrus.Warnf(`Handler::withRetry: event %#v has been dropped due to too many retries`, event) + slog.Warn(`Handler::withRetry: event has been dropped due to too many retries`, "event", event) } } // TODO: Add error logging } diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 9cc9a16..c4ea32b 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -9,11 +9,11 @@ import ( "context" "errors" "fmt" + "log/slog" "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -54,7 +54,7 @@ func NewWatcher( // Initialize initializes the Watcher, must be called before Watch func (w *Watcher) Initialize(ctx context.Context) error { - logrus.Debug("Watcher::Initialize") + slog.Debug("Watcher::Initialize") var err error w.informer = w.buildInformer() @@ -70,7 +70,7 @@ func (w *Watcher) Initialize(ctx context.Context) error { // Watch starts the process of watching for changes to Kubernetes resources. // Initialize must be called before Watch. func (w *Watcher) Watch(ctx context.Context) error { - logrus.Debug("Watcher::Watch") + slog.Debug("Watcher::Watch") if w.informer == nil { return errors.New("error: Initialize must be called before Watch") @@ -106,7 +106,7 @@ func (w *Watcher) isDesiredService(service *v1.Service) bool { // buildEventHandlerForAdd creates a function that is used as an event handler // for the informer when Add events are raised. func (w *Watcher) buildEventHandlerForAdd(ctx context.Context) func(interface{}) { - logrus.Info("Watcher::buildEventHandlerForAdd") + slog.Info("Watcher::buildEventHandlerForAdd") return func(obj interface{}) { service := obj.(*v1.Service) if !w.isDesiredService(service) { @@ -115,7 +115,7 @@ func (w *Watcher) buildEventHandlerForAdd(ctx context.Context) func(interface{}) nodeIps, err := w.retrieveNodeIps(ctx) if err != nil { - logrus.Errorf(`error occurred retrieving node ips: %v`, err) + slog.Error("error occurred retrieving node ips", "error", err) return } @@ -128,7 +128,7 @@ func (w *Watcher) buildEventHandlerForAdd(ctx context.Context) func(interface{}) // buildEventHandlerForDelete creates a function that is used as an event handler // for the informer when Delete events are raised. func (w *Watcher) buildEventHandlerForDelete(ctx context.Context) func(interface{}) { - logrus.Info("Watcher::buildEventHandlerForDelete") + slog.Info("Watcher::buildEventHandlerForDelete") return func(obj interface{}) { service := obj.(*v1.Service) if !w.isDesiredService(service) { @@ -137,7 +137,7 @@ func (w *Watcher) buildEventHandlerForDelete(ctx context.Context) func(interface nodeIps, err := w.retrieveNodeIps(ctx) if err != nil { - logrus.Errorf(`error occurred retrieving node ips: %v`, err) + slog.Error("error occurred retrieving node ips", "error", err) return } @@ -150,7 +150,7 @@ func (w *Watcher) buildEventHandlerForDelete(ctx context.Context) func(interface // buildEventHandlerForUpdate creates a function that is used as an event handler // for the informer when Update events are raised. func (w *Watcher) buildEventHandlerForUpdate(ctx context.Context) func(interface{}, interface{}) { - logrus.Info("Watcher::buildEventHandlerForUpdate") + slog.Info("Watcher::buildEventHandlerForUpdate") return func(previous, updated interface{}) { // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API service := updated.(*v1.Service) @@ -160,7 +160,7 @@ func (w *Watcher) buildEventHandlerForUpdate(ctx context.Context) func(interface nodeIps, err := w.retrieveNodeIps(ctx) if err != nil { - logrus.Errorf(`error occurred retrieving node ips: %v`, err) + slog.Error("error occurred retrieving node ips", "error", err) return } @@ -172,7 +172,7 @@ func (w *Watcher) buildEventHandlerForUpdate(ctx context.Context) func(interface // buildInformer creates the informer used to watch for changes to Kubernetes resources. func (w *Watcher) buildInformer() cache.SharedIndexInformer { - logrus.Debug("Watcher::buildInformer") + slog.Debug("Watcher::buildInformer") factory := informers.NewSharedInformerFactoryWithOptions( w.k8sClient, w.settings.Watcher.ResyncPeriod, @@ -184,7 +184,7 @@ func (w *Watcher) buildInformer() cache.SharedIndexInformer { // initializeEventListeners initializes the event listeners for the informer. func (w *Watcher) initializeEventListeners(ctx context.Context) error { - logrus.Debug("Watcher::initializeEventListeners") + slog.Debug("Watcher::initializeEventListeners") var err error handlers := cache.ResourceEventHandlerFuncs{ @@ -205,13 +205,13 @@ func (w *Watcher) initializeEventListeners(ctx context.Context) error { // because the master node may or may not be a worker node and thus may not be able to route traffic. func (w *Watcher) retrieveNodeIps(ctx context.Context) ([]string, error) { started := time.Now() - logrus.Debug("Watcher::retrieveNodeIps") + slog.Debug("Watcher::retrieveNodeIps") var nodeIps []string nodes, err := w.k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) if err != nil { - logrus.Errorf(`error occurred retrieving the list of nodes: %v`, err) + slog.Error("error occurred retrieving the list of nodes", "error", err) return nil, err } @@ -226,14 +226,14 @@ func (w *Watcher) retrieveNodeIps(ctx context.Context) ([]string, error) { } } - logrus.Debugf("Watcher::retrieveNodeIps duration: %d", time.Since(started).Nanoseconds()) + slog.Debug("Watcher::retrieveNodeIps duration", "duration", time.Since(started).Nanoseconds()) return nodeIps, nil } // notMasterNode determines if the node is a master node. func (w *Watcher) notMasterNode(node v1.Node) bool { - logrus.Debug("Watcher::notMasterNode") + slog.Debug("Watcher::notMasterNode") _, found := node.Labels["node-role.kubernetes.io/master"] diff --git a/internal/probation/server.go b/internal/probation/server.go index 84b5b67..9520fc7 100644 --- a/internal/probation/server.go +++ b/internal/probation/server.go @@ -7,10 +7,9 @@ package probation import ( "fmt" + "log/slog" "net/http" "time" - - "github.com/sirupsen/logrus" ) const ( @@ -27,7 +26,6 @@ const ( // HealthServer is a server that spins up endpoints for the various k8s health checks. type HealthServer struct { - // The underlying HTTP server. httpServer *http.Server @@ -52,7 +50,7 @@ func NewHealthServer() *HealthServer { // Start spins up the health server. func (hs *HealthServer) Start() { - logrus.Debugf("Starting probe listener on port %d", ListenPort) + slog.Debug("Starting probe listener", "port", ListenPort) address := fmt.Sprintf(":%d", ListenPort) @@ -64,17 +62,17 @@ func (hs *HealthServer) Start() { go func() { if err := hs.httpServer.ListenAndServe(); err != nil { - logrus.Errorf("unable to start probe listener on %s: %v", hs.httpServer.Addr, err) + slog.Error("unable to start probe listener", "address", hs.httpServer.Addr, "error", err) } }() - logrus.Info("Started probe listener on", hs.httpServer.Addr) + slog.Info("Started probe listener", "address", hs.httpServer.Addr) } // Stop shuts down the health server. func (hs *HealthServer) Stop() { if err := hs.httpServer.Close(); err != nil { - logrus.Errorf("unable to stop probe listener on %s: %v", hs.httpServer.Addr, err) + slog.Error("unable to stop probe listener", "address", hs.httpServer.Addr, "error", err) } } @@ -99,14 +97,14 @@ func (hs *HealthServer) handleProbe(writer http.ResponseWriter, _ *http.Request, writer.WriteHeader(http.StatusOK) if _, err := fmt.Fprint(writer, Ok); err != nil { - logrus.Error(err) + slog.Error(err.Error()) } } else { writer.WriteHeader(http.StatusServiceUnavailable) if _, err := fmt.Fprint(writer, ServiceNotAvailable); err != nil { - logrus.Error(err) + slog.Error(err.Error()) } } } diff --git a/internal/probation/server_test.go b/internal/probation/server_test.go index 9c7d37d..981aa3b 100644 --- a/internal/probation/server_test.go +++ b/internal/probation/server_test.go @@ -6,11 +6,11 @@ package probation import ( + "log/slog" "net/http" "testing" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" - "github.com/sirupsen/logrus" ) func TestHealthServer_HandleLive(t *testing.T) { @@ -76,5 +76,5 @@ func TestHealthServer_Start(t *testing.T) { t.Errorf("Expected status code %v, got %v", http.StatusAccepted, response.StatusCode) } - logrus.Infof("received a response from the probe server: %v", response) + slog.Info("received a response from the probe server", "response", response) } diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index eadfbd8..20bc99a 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -7,13 +7,13 @@ package synchronization import ( "fmt" + "log/slog" "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" "github.com/nginxinc/kubernetes-nginx-ingress/internal/communication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" nginxClient "github.com/nginxinc/nginx-plus-go-client/client" - "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/workqueue" ) @@ -58,10 +58,10 @@ func NewSynchronizer( // AddEvents adds a list of events to the queue. If no hosts are specified this is a null operation. // Events will fan out to the number of hosts specified before being added to the queue. func (s *Synchronizer) AddEvents(events core.ServerUpdateEvents) { - logrus.Debugf(`Synchronizer::AddEvents adding %d events`, len(events)) + slog.Debug(`Synchronizer::AddEvents adding events`, slog.Int("eventCount", len(events))) if len(s.settings.NginxPlusHosts) == 0 { - logrus.Warnf(`No Nginx Plus hosts were specified. Skipping synchronization.`) + slog.Warn(`No Nginx Plus hosts were specified. Skipping synchronization.`) return } @@ -75,10 +75,10 @@ func (s *Synchronizer) AddEvents(events core.ServerUpdateEvents) { // AddEvent adds an event to the queue. If no hosts are specified this is a null operation. // Events will be added to the queue after a random delay between MinMillisecondsJitter and MaxMillisecondsJitter. func (s *Synchronizer) AddEvent(event *core.ServerUpdateEvent) { - logrus.Debugf(`Synchronizer::AddEvent: %#v`, event) + slog.Debug(`Synchronizer::AddEvent`, "event", event) if event.NginxHost == `` { - logrus.Warnf(`Nginx host was not specified. Skipping synchronization.`) + slog.Warn(`Nginx host was not specified. Skipping synchronization.`) return } @@ -91,7 +91,7 @@ func (s *Synchronizer) AddEvent(event *core.ServerUpdateEvent) { // Run starts the Synchronizer, spins up Goroutines to process events, and waits for a stop signal. func (s *Synchronizer) Run(stopCh <-chan struct{}) { - logrus.Debug(`Synchronizer::Run`) + slog.Debug(`Synchronizer::Run`) for i := 0; i < s.settings.Synchronizer.Threads; i++ { go wait.Until(s.worker, 0, stopCh) @@ -102,7 +102,7 @@ func (s *Synchronizer) Run(stopCh <-chan struct{}) { // ShutDown stops the Synchronizer and shuts down the event queue func (s *Synchronizer) ShutDown() { - logrus.Debugf(`Synchronizer::ShutDown`) + slog.Debug(`Synchronizer::ShutDown`) s.eventQueue.ShutDownWithDrain() } @@ -110,7 +110,7 @@ func (s *Synchronizer) ShutDown() { // NOTE: There is an open issue (https://github.com/nginxinc/nginx-loadbalancer-kubernetes/issues/36) to move creation // of the underlying Border Server client to the NewBorderClient function. func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (application.Interface, error) { - logrus.Debugf(`Synchronizer::buildBorderClient`) + slog.Debug(`Synchronizer::buildBorderClient`) var err error @@ -129,7 +129,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica // fanOutEventToHosts takes a list of events and returns a list of events, one for each Border Server. func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.ServerUpdateEvents { - logrus.Debugf(`Synchronizer::fanOutEventToHosts: %#v`, event) + slog.Debug(`Synchronizer::fanOutEventToHosts`, "event", event) var events core.ServerUpdateEvents @@ -147,7 +147,7 @@ func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.Se // handleEvent dispatches an event to the proper handler function. func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { - logrus.Debugf(`Synchronizer::handleEvent: Id: %s`, event.ID) + slog.Debug(`Synchronizer::handleEvent`, slog.String("eventID", event.ID)) var err error @@ -162,13 +162,13 @@ func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { err = s.handleDeletedEvent(event) default: - logrus.Warnf(`Synchronizer::handleEvent: unknown event type: %d`, event.Type) + slog.Warn(`Synchronizer::handleEvent: unknown event type`, "type", event.Type) } if err == nil { - logrus.Infof( - `Synchronizer::handleEvent: successfully %s the nginx+ host(s) for Upstream: %s: Id(%s)`, - event.TypeName(), event.UpstreamName, event.ID) + slog.Info( + "Synchronizer::handleEvent: successfully handled the event", + "type", event.TypeName(), "upstreamName", event.UpstreamName, "eventID", event.ID) } return err @@ -176,7 +176,7 @@ func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { // handleCreatedUpdatedEvent handles events of type Created or Updated. func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerUpdateEvent) error { - logrus.Debugf(`Synchronizer::handleCreatedUpdatedEvent: Id: %s`, serverUpdateEvent.ID) + slog.Debug(`Synchronizer::handleCreatedUpdatedEvent`, "eventID", serverUpdateEvent.ID) var err error @@ -194,7 +194,7 @@ func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerU // handleDeletedEvent handles events of type Deleted. func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEvent) error { - logrus.Debugf(`Synchronizer::handleDeletedEvent: Id: %s`, serverUpdateEvent.ID) + slog.Debug(`Synchronizer::handleDeletedEvent`, "eventID", serverUpdateEvent.ID) var err error @@ -212,7 +212,7 @@ func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEv // handleNextEvent pulls an event from the event queue and feeds it to the event handler with retry logic func (s *Synchronizer) handleNextEvent() bool { - logrus.Debug(`Synchronizer::handleNextEvent`) + slog.Debug(`Synchronizer::handleNextEvent`) evt, quit := s.eventQueue.Get() if quit { @@ -229,18 +229,18 @@ func (s *Synchronizer) handleNextEvent() bool { // worker is the main message loop func (s *Synchronizer) worker() { - logrus.Debug(`Synchronizer::worker`) + slog.Debug(`Synchronizer::worker`) for s.handleNextEvent() { } } // withRetry handles errors from the event handler and requeues events that fail func (s *Synchronizer) withRetry(err error, event *core.ServerUpdateEvent) { - logrus.Debug("Synchronizer::withRetry") + slog.Debug("Synchronizer::withRetry") if err != nil { // TODO: Add Telemetry s.eventQueue.AddRateLimited(event) - logrus.Infof(`Synchronizer::withRetry: requeued event: %s; error: %v`, event.ID, err) + slog.Info(`Synchronizer::withRetry: requeued event`, "eventID", event.ID, "error", err) } else { s.eventQueue.Forget(event) } // TODO: Add error logging diff --git a/internal/translation/translator.go b/internal/translation/translator.go index fe8532c..dceecf5 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -7,17 +7,17 @@ package translation import ( "fmt" + "log/slog" "strings" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" ) // Translate transforms event data into an intermediate format that can be consumed by the BorderClient implementations // and used to update the Border Servers. func Translate(event *core.Event) (core.ServerUpdateEvents, error) { - logrus.Debug("Translate::Translate") + slog.Debug("Translate::Translate") return buildServerUpdateEvents(event.Service.Spec.Ports, event) } @@ -29,13 +29,13 @@ func Translate(event *core.Event) (core.ServerUpdateEvents, error) { // The NGINX+ Client uses a single server for Deleted events; // so the list of servers is broken up into individual events. func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.ServerUpdateEvents, error) { - logrus.Debugf("Translate::buildServerUpdateEvents(ports=%#v)", ports) + slog.Debug("Translate::buildServerUpdateEvents", "ports", ports) events := core.ServerUpdateEvents{} for _, port := range ports { context, upstreamName, err := getContextAndUpstreamName(port) if err != nil { - logrus.Info(err) + slog.Info(err.Error()) continue } @@ -56,7 +56,7 @@ func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.Se } default: - logrus.Warnf(`Translator::buildServerUpdateEvents: unknown event type: %d`, event.Type) + slog.Warn(`Translator::buildServerUpdateEvents: unknown event type`, "type", event.Type) } } From 98a140f75f1fd2edbf5d73550fc62adaeba0c56c Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 16 Oct 2024 15:31:59 -0600 Subject: [PATCH 078/136] NLB-6753 Added nlk version to outgoing request headers --- internal/communication/factory.go | 2 ++ internal/communication/factory_test.go | 16 ++++++++++++---- internal/communication/roundtripper_test.go | 10 +++++++--- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/communication/factory.go b/internal/communication/factory.go index 8664e9d..eec593b 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -14,6 +14,7 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" ) // NewHTTPClient is a factory method to create a new Http Client with a default configuration. @@ -38,6 +39,7 @@ func NewHeaders(apiKey string) []string { headers := []string{ "Content-Type: application/json", "Accept: application/json", + fmt.Sprintf("X-NLK-Version: %s", buildinfo.SemVer()), } if apiKey != "" { diff --git a/internal/communication/factory_test.go b/internal/communication/factory_test.go index 65f5e5b..7562484 100644 --- a/internal/communication/factory_test.go +++ b/internal/communication/factory_test.go @@ -31,7 +31,7 @@ func TestNewHeaders(t *testing.T) { t.Fatalf(`headers should not be nil`) } - if len(headers) != 3 { + if len(headers) != 4 { t.Fatalf(`headers should have 3 elements`) } @@ -43,8 +43,12 @@ func TestNewHeaders(t *testing.T) { t.Fatalf(`headers[1] should be "Accept: application/json"`) } - if headers[2] != "Authorization: ApiKey fakeKey" { - t.Fatalf(`headers[2] should be "Accept: Authorization: ApiKey fakeKey"`) + if headers[2] != "X-NLK-Version: " { + t.Fatalf(`headers[2] should be "X-NLK-Version: "`) + } + + if headers[3] != "Authorization: ApiKey fakeKey" { + t.Fatalf(`headers[3] should be "Accept: Authorization: ApiKey fakeKey"`) } } @@ -56,7 +60,7 @@ func TestNewHeadersWithNoAPIKey(t *testing.T) { t.Fatalf(`headers should not be nil`) } - if len(headers) != 2 { + if len(headers) != 3 { t.Fatalf(`headers should have 2 elements`) } @@ -67,6 +71,10 @@ func TestNewHeadersWithNoAPIKey(t *testing.T) { if headers[1] != "Accept: application/json" { t.Fatalf(`headers[1] should be "Accept: application/json"`) } + + if headers[2] != "X-NLK-Version: " { + t.Fatalf(`headers[2] should be "X-NLK-Version: "`) + } } func TestNewTransport(t *testing.T) { diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index 9913600..55dea88 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -28,7 +28,7 @@ func TestNewRoundTripper(t *testing.T) { t.Fatalf(`roundTripper.Headers should not be nil`) } - if len(roundTripper.Headers) != 3 { + if len(roundTripper.Headers) != 4 { t.Fatalf(`roundTripper.Headers should have 3 elements`) } @@ -40,8 +40,12 @@ func TestNewRoundTripper(t *testing.T) { t.Fatalf(`roundTripper.Headers[1] should be "Accept: application/json"`) } - if roundTripper.Headers[2] != "Authorization: ApiKey fakeKey" { - t.Fatalf(`headers[2] should be "Accept: Authorization: ApiKey fakeKey"`) + if roundTripper.Headers[2] != "X-NLK-Version: " { + t.Fatalf(`roundTripper.Headers[2] should be "X-NLK-Version: "`) + } + + if roundTripper.Headers[3] != "Authorization: ApiKey fakeKey" { + t.Fatalf(`roundTripper.Headers[3] should be "Accept: Authorization: ApiKey fakeKey"`) } if roundTripper.RoundTripper == nil { From 58053e8154a5b301509a318047b462886a554cf5 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 16 Oct 2024 15:18:04 -0600 Subject: [PATCH 079/136] NLB-5753 Bumped to version 0.6.1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index a918a2a..ee6cdce 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.6.0 +0.6.1 From c35132334d4ff56ef0603e0f585222b4c5fd3543 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 7 Nov 2024 01:51:32 +0530 Subject: [PATCH 080/136] NLB-5716: Allow devs to deploy NLK to AKS This commit adds a make target that bootstraps NLK via helm using: - charts from the dev-feature branch. - image from the dev-feature branch. The workflow can be exercised against an AKS cluster and we can interact with NLK via helm post bootstrapping like any user of the controller. --- Makefile | 7 ++++++- scripts/deploy.sh | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100755 scripts/deploy.sh diff --git a/Makefile b/Makefile index 08e07ce..4d00656 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ DOCKER_TAG ?= latest GOPRIVATE = *.f5net.com,gitlab.com/f5 export GOPRIVATE -.PHONY: default tools deps fmt lint test build build.docker publish helm-lint +.PHONY: default tools deps fmt lint test build build.docker publish helm-lint deploy default: build @@ -58,6 +58,11 @@ publish: build-linux build-linux-docker @scripts/docker-login.sh @./scripts/docker.sh publish +deploy: + @[ -f $(KUBECONFIG) ] || (echo "KUBECONFIG not found." && false) + $(MAKE) publish + @scripts/deploy.sh + clean: rm -rf $(BUILD_DIR)/ diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..dc8a56a --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -eo pipefail + + +if [ -z "$KUBECONFIG" ]; then + echo "KUBECONFIG is not set." + exit 1 +fi +if [ ! -e "$KUBECONFIG" ]; then + echo "KUBECONFIG does not exist." + exit 1 +fi + +root_dir=$(git rev-parse --show-toplevel) +# shellcheck source=/dev/null +source "${root_dir}/.devops.sh" +devops.backend.docker.set "azure.container-registry-dev" +devops.backend.docker.authenticate + +namespace="nlk" +helm_release_name="release-1" +registry="${DEVOPS_DOCKER_URL}" +repository="nginx-azure-lb/nginxaas-loadbalancer-kubernetes/nginxaas-loadbalancer-kubernetes" +image_tag=$(git rev-parse --short=8 HEAD) + +kubectl create namespace "${namespace}" --dry-run=client -o yaml | kubectl apply -f - +kubectl -n "${namespace}" create secret docker-registry regcred \ + --docker-username="${DEVOPS_DOCKER_USER}" \ + --docker-password="${DEVOPS_DOCKER_PASS}" \ + --docker-server="${DEVOPS_DOCKER_URL}" \ + --dry-run=client -o yaml | kubectl apply -f - + +helm -n "$namespace" upgrade "$helm_release_name" ${root_dir}/charts/nlk/ \ + --set nlk.image.registry="${registry}",nlk.image.repository="${repository}",nlk.image.tag="${image_tag}",nlk.imagePullSecrets[0].name=regcred \ + --install \ + --reuse-values \ + --wait \ + --timeout 2m From 62241befaf49d22624c7538f835b8bdbf98f2ef6 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 7 Nov 2024 02:10:12 +0530 Subject: [PATCH 081/136] Update AKS API version in marketplace bundle The marketplace package needs to have the AKS API as the latest or anything that is under 2 years. Very conservatively choosing the latest version from last year. --- charts/armTemplate.json | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/armTemplate.json b/charts/armTemplate.json index 510c784..06c3703 100644 --- a/charts/armTemplate.json +++ b/charts/armTemplate.json @@ -164,7 +164,7 @@ { "type": "Microsoft.ContainerService/managedClusters", "condition": "[parameters('createNewCluster')]", - "apiVersion": "2022-11-01", + "apiVersion": "2023-11-01", "name": "[parameters('clusterResourceName')]", "location": "[parameters('location')]", "dependsOn": [], diff --git a/version b/version index ee6cdce..b616048 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.6.1 +0.6.2 From b42d27935847588bb140ad4695792276d15643ec Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 7 Nov 2024 12:40:33 -0700 Subject: [PATCH 082/136] NLB-4468 Added package pointer from ARP --- pkg/pointer/pointer.go | 56 +++++++++++++++++++++++++++++++++ pkg/pointer/pointer_test.go | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 pkg/pointer/pointer.go create mode 100644 pkg/pointer/pointer_test.go diff --git a/pkg/pointer/pointer.go b/pkg/pointer/pointer.go new file mode 100644 index 0000000..08ff667 --- /dev/null +++ b/pkg/pointer/pointer.go @@ -0,0 +1,56 @@ +// Package pointer provides utilities that assist in working with pointers. +package pointer + +// To returns a pointer to the given value +func To[T any](v T) *T { return &v } + +// From dereferences the pointer if it is not nil or returns d +func From[T any](p *T, d T) T { + if p != nil { + return *p + } + return d +} + +// ToSlice returns a slice of pointers to the given values. +func ToSlice[T any](values []T) []*T { + if len(values) == 0 { + return nil + } + ret := make([]*T, 0, len(values)) + for _, v := range values { + v := v + ret = append(ret, &v) + } + return ret +} + +// FromSlice returns a slice of values to the given pointers, dropping any nils. +func FromSlice[T any](values []*T) []T { + if len(values) == 0 { + return nil + } + ret := make([]T, 0, len(values)) + for _, v := range values { + if v != nil { + ret = append(ret, *v) + } + } + return ret +} + +// Equal reports if p is a pointer to a value equal to v +func Equal[T comparable](p *T, v T) bool { + if p == nil { + return false + } + return *p == v +} + +// ValueEqual reports if value of pointer referenced by p is equal to value of pointer referenced by q +func ValueEqual[T comparable](p *T, q *T) bool { + if p == nil || q == nil { + return p == q + } + return *p == *q +} diff --git a/pkg/pointer/pointer_test.go b/pkg/pointer/pointer_test.go new file mode 100644 index 0000000..e929e58 --- /dev/null +++ b/pkg/pointer/pointer_test.go @@ -0,0 +1,62 @@ +package pointer_test + +import ( + "testing" + + "github.com/nginxinc/kubernetes-nginx-ingress/pkg/pointer" + "github.com/stretchr/testify/require" +) + +func TestTo(t *testing.T) { + t.Parallel() + + for _, v := range []string{"", "hello"} { + require.Equal(t, v, *pointer.To(v)) + } + for _, v := range []int{0, 123456, -123456} { + require.Equal(t, v, *pointer.To(v)) + } + for _, v := range []int64{0, 123456, -123456} { + require.Equal(t, v, *pointer.To(v)) + } +} + +func TestFrom(t *testing.T) { + t.Parallel() + + sv := "s" + sd := "default" + require.Equal(t, sd, pointer.From(nil, sd)) + require.Equal(t, sv, pointer.From(&sv, sd)) + + iv := 1 + id := 2 + require.Equal(t, id, pointer.From(nil, id)) + require.Equal(t, iv, pointer.From(&iv, id)) + + i64v := int64(1) + i64d := int64(2) + require.Equal(t, i64d, pointer.From(nil, i64d)) + require.Equal(t, i64v, pointer.From(&i64v, i64d)) +} + +func TestToSlice_FromSlice(t *testing.T) { + t.Parallel() + + v := []int{1, 2, 3} + require.Equal(t, v, pointer.FromSlice(pointer.ToSlice(v))) + require.Nil(t, pointer.ToSlice([]string{})) + require.Nil(t, pointer.FromSlice([]*string{})) + require.Equal(t, []string{"A", "B"}, pointer.FromSlice([]*string{pointer.To("A"), nil, pointer.To("B")})) +} + +func TestEqual(t *testing.T) { + t.Parallel() + + require.True(t, pointer.Equal(pointer.To(1), 1)) + require.False(t, pointer.Equal(nil, 1)) + require.False(t, pointer.Equal(pointer.To(1), 2)) + + s := new(struct{}) + require.False(t, pointer.Equal(&s, nil)) +} From a9e2600bd18bf9caaf4bfbcd166b7ce855ee00b1 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 31 Oct 2024 07:13:17 -0600 Subject: [PATCH 083/136] NLB-4468 Added support for cluster IP services, service IP addresses fetched by translator The watcher's role no longer includes sending node IPs to the translator. The watcher simply alerts the translator of a change event with respect to a specific service. It is now the translator's role to determine the upstream server addresses from the nodeport IPs or the cluster IPs, depending on the service type. There were a number of reasons for this change. The main one is that any transitory failure to fetch IP addresses using the kubernetes client will cause the update event to be readded to the workqueue, instead of abandoned. Another benefit is that we can now leverage the existing unit tests for the translator to test the cluster IP functionality here. This commit adds support for cluster IP services. The watcher fetches the service IP addresses and ports from the endpoint slices relevant to the service. The other main change here is to the way that events are handled when a service is deleted. Instead of issuing multiple requests to the nginx hosts to delete specific upstream servers, we now send a single update servers request with an empty list of servers. The nginx plus client extrapolates from the empty list and deletes all existing servers for the upstream. --- cmd/nginx-loadbalancer-kubernetes/main.go | 7 +- go.mod | 2 +- internal/core/event.go | 8 +- internal/core/event_test.go | 7 +- internal/observation/handler.go | 41 +- internal/observation/handler_test.go | 11 +- internal/observation/watcher.go | 83 +- internal/translation/translator.go | 170 ++- internal/translation/translator_test.go | 1489 +++++++++++++++------ test/mocks/mock_handler.go | 15 +- 10 files changed, 1316 insertions(+), 517 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 8064348..00be5d4 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -15,6 +15,7 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/observation" "github.com/nginxinc/kubernetes-nginx-ingress/internal/probation" "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/translation" "github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -54,19 +55,19 @@ func run() error { handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) - handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue) + handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translation.NewTranslator(k8sClient)) watcher, err := observation.NewWatcher(settings, handler, k8sClient) if err != nil { return fmt.Errorf(`error occurred creating a watcher: %w`, err) } - err = watcher.Initialize(ctx) + err = watcher.Initialize() if err != nil { return fmt.Errorf(`error occurred initializing the watcher: %w`, err) } - go handler.Run(ctx.Done()) + go handler.Run(ctx) go synchronizer.Run(ctx.Done()) probeServer := probation.NewHealthServer() diff --git a/go.mod b/go.mod index 94a1319..6b26183 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/nginxinc/nginx-plus-go-client v1.2.2 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 + golang.org/x/net v0.23.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 @@ -54,7 +55,6 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect diff --git a/internal/core/event.go b/internal/core/event.go index 09776c9..16d5d94 100644 --- a/internal/core/event.go +++ b/internal/core/event.go @@ -24,7 +24,6 @@ const ( // Event represents a service event type Event struct { - // Type represents the event type, one of the constant values defined above. Type EventType @@ -33,18 +32,13 @@ type Event struct { // PreviousService represents the service object in its previous state PreviousService *v1.Service - - // NodeIps represents the list of node IPs in the Cluster. This is populated by the Watcher when an event is created. - // The Node IPs are needed by the BorderClient. - NodeIps []string } // NewEvent factory method to create a new Event -func NewEvent(eventType EventType, service *v1.Service, previousService *v1.Service, nodeIps []string) Event { +func NewEvent(eventType EventType, service *v1.Service, previousService *v1.Service) Event { return Event{ Type: eventType, Service: service, PreviousService: previousService, - NodeIps: nodeIps, } } diff --git a/internal/core/event_test.go b/internal/core/event_test.go index 662eb8f..f0184fb 100644 --- a/internal/core/event_test.go +++ b/internal/core/event_test.go @@ -11,9 +11,8 @@ func TestNewEvent(t *testing.T) { expectedType := Created expectedService := &v1.Service{} expectedPreviousService := &v1.Service{} - expectedNodeIps := []string{"127.0.0.1"} - event := NewEvent(expectedType, expectedService, expectedPreviousService, expectedNodeIps) + event := NewEvent(expectedType, expectedService, expectedPreviousService) if event.Type != expectedType { t.Errorf("expected Created, got %v", event.Type) @@ -26,8 +25,4 @@ func TestNewEvent(t *testing.T) { if event.PreviousService != expectedPreviousService { t.Errorf("expected previous service, got %#v", event.PreviousService) } - - if event.NodeIps[0] != expectedNodeIps[0] { - t.Errorf("expected node ips, got %#v", event.NodeIps) - } } diff --git a/internal/observation/handler.go b/internal/observation/handler.go index 9f44359..2b5bcfb 100644 --- a/internal/observation/handler.go +++ b/internal/observation/handler.go @@ -6,13 +6,13 @@ package observation import ( + "context" "fmt" "log/slog" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/translation" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/workqueue" ) @@ -23,7 +23,7 @@ type HandlerInterface interface { AddRateLimitedEvent(event *core.Event) // Run defines the interface used to start the event handler - Run(stopCh <-chan struct{}) + Run(ctx context.Context) // ShutDown defines the interface used to stop the event handler ShutDown() @@ -42,6 +42,12 @@ type Handler struct { // synchronizer is the synchronizer used to synchronize the internal representation with a Border Server synchronizer synchronization.Interface + + translator Translator +} + +type Translator interface { + Translate(context.Context, *core.Event) (core.ServerUpdateEvents, error) } // NewHandler creates a new event handler @@ -49,11 +55,13 @@ func NewHandler( settings configuration.Settings, synchronizer synchronization.Interface, eventQueue workqueue.RateLimitingInterface, + translator Translator, ) *Handler { return &Handler{ eventQueue: eventQueue, settings: settings, synchronizer: synchronizer, + translator: translator, } } @@ -63,15 +71,21 @@ func (h *Handler) AddRateLimitedEvent(event *core.Event) { h.eventQueue.AddRateLimited(event) } -// Run starts the event handler, spins up Goroutines to process events, and waits for a stop signal -func (h *Handler) Run(stopCh <-chan struct{}) { +// Run starts the event handler, spins up Goroutines to process events, and waits for context to be done +func (h *Handler) Run(ctx context.Context) { slog.Debug("Handler::Run") + worker := func() { + for h.handleNextEvent(ctx) { + // TODO: Add Telemetry + } + } + for i := 0; i < h.settings.Handler.Threads; i++ { - go wait.Until(h.worker, 0, stopCh) + go wait.Until(worker, 0, ctx.Done()) } - <-stopCh + <-ctx.Done() } // ShutDown stops the event handler and shuts down the event queue @@ -81,11 +95,11 @@ func (h *Handler) ShutDown() { } // handleEvent feeds translated events to the synchronizer -func (h *Handler) handleEvent(e *core.Event) error { +func (h *Handler) handleEvent(ctx context.Context, e *core.Event) error { slog.Debug("Handler::handleEvent", "event", e) // TODO: Add Telemetry - events, err := translation.Translate(e) + events, err := h.translator.Translate(ctx, e) if err != nil { return fmt.Errorf(`Handler::handleEvent error translating: %v`, err) } @@ -96,7 +110,7 @@ func (h *Handler) handleEvent(e *core.Event) error { } // handleNextEvent pulls an event from the event queue and feeds it to the event handler with retry logic -func (h *Handler) handleNextEvent() bool { +func (h *Handler) handleNextEvent(ctx context.Context) bool { evt, quit := h.eventQueue.Get() slog.Debug("Handler::handleNextEvent", "event", evt, "quit", quit) if quit { @@ -106,18 +120,11 @@ func (h *Handler) handleNextEvent() bool { defer h.eventQueue.Done(evt) event := evt.(*core.Event) - h.withRetry(h.handleEvent(event), event) + h.withRetry(h.handleEvent(ctx, event), event) return true } -// worker is the main message loop -func (h *Handler) worker() { - for h.handleNextEvent() { - // TODO: Add Telemetry - } -} - // withRetry handles errors from the event handler and requeues events that fail func (h *Handler) withRetry(err error, event *core.Event) { slog.Debug("Handler::withRetry") diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go index ba4add1..9dd736c 100644 --- a/internal/observation/handler_test.go +++ b/internal/observation/handler_test.go @@ -6,6 +6,7 @@ package observation import ( + "context" "testing" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" @@ -33,7 +34,7 @@ func TestHandler_AddsEventToSynchronizer(t *testing.T) { handler.AddRateLimitedEvent(event) - handler.handleNextEvent() + handler.handleNextEvent(context.Background()) if len(synchronizer.Events) != 1 { t.Errorf(`handler.AddRateLimitedEvent did not add the event to the queue`) @@ -46,7 +47,13 @@ func buildHandler() ( eventQueue := &mocks.MockRateLimiter{} synchronizer := &mocks.MockSynchronizer{} - handler := NewHandler(configuration.Settings{}, synchronizer, eventQueue) + handler := NewHandler(configuration.Settings{}, synchronizer, eventQueue, &fakeTranslator{}) return synchronizer, handler } + +type fakeTranslator struct{} + +func (t *fakeTranslator) Translate(ctx context.Context, event *core.Event) (core.ServerUpdateEvents, error) { + return core.ServerUpdateEvents{{}}, nil +} diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index c4ea32b..21ab068 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -10,12 +10,10 @@ import ( "errors" "fmt" "log/slog" - "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -53,13 +51,13 @@ func NewWatcher( } // Initialize initializes the Watcher, must be called before Watch -func (w *Watcher) Initialize(ctx context.Context) error { +func (w *Watcher) Initialize() error { slog.Debug("Watcher::Initialize") var err error w.informer = w.buildInformer() - err = w.initializeEventListeners(ctx) + err = w.initializeEventListeners() if err != nil { return fmt.Errorf(`initialization error: %w`, err) } @@ -105,7 +103,7 @@ func (w *Watcher) isDesiredService(service *v1.Service) bool { // buildEventHandlerForAdd creates a function that is used as an event handler // for the informer when Add events are raised. -func (w *Watcher) buildEventHandlerForAdd(ctx context.Context) func(interface{}) { +func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { slog.Info("Watcher::buildEventHandlerForAdd") return func(obj interface{}) { service := obj.(*v1.Service) @@ -113,21 +111,15 @@ func (w *Watcher) buildEventHandlerForAdd(ctx context.Context) func(interface{}) return } - nodeIps, err := w.retrieveNodeIps(ctx) - if err != nil { - slog.Error("error occurred retrieving node ips", "error", err) - return - } - var previousService *v1.Service - e := core.NewEvent(core.Created, service, previousService, nodeIps) + e := core.NewEvent(core.Created, service, previousService) w.handler.AddRateLimitedEvent(&e) } } // buildEventHandlerForDelete creates a function that is used as an event handler // for the informer when Delete events are raised. -func (w *Watcher) buildEventHandlerForDelete(ctx context.Context) func(interface{}) { +func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { slog.Info("Watcher::buildEventHandlerForDelete") return func(obj interface{}) { service := obj.(*v1.Service) @@ -135,21 +127,15 @@ func (w *Watcher) buildEventHandlerForDelete(ctx context.Context) func(interface return } - nodeIps, err := w.retrieveNodeIps(ctx) - if err != nil { - slog.Error("error occurred retrieving node ips", "error", err) - return - } - var previousService *v1.Service - e := core.NewEvent(core.Deleted, service, previousService, nodeIps) + e := core.NewEvent(core.Deleted, service, previousService) w.handler.AddRateLimitedEvent(&e) } } // buildEventHandlerForUpdate creates a function that is used as an event handler // for the informer when Update events are raised. -func (w *Watcher) buildEventHandlerForUpdate(ctx context.Context) func(interface{}, interface{}) { +func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { slog.Info("Watcher::buildEventHandlerForUpdate") return func(previous, updated interface{}) { // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API @@ -158,14 +144,8 @@ func (w *Watcher) buildEventHandlerForUpdate(ctx context.Context) func(interface return } - nodeIps, err := w.retrieveNodeIps(ctx) - if err != nil { - slog.Error("error occurred retrieving node ips", "error", err) - return - } - previousService := previous.(*v1.Service) - e := core.NewEvent(core.Updated, service, previousService, nodeIps) + e := core.NewEvent(core.Updated, service, previousService) w.handler.AddRateLimitedEvent(&e) } } @@ -183,14 +163,14 @@ func (w *Watcher) buildInformer() cache.SharedIndexInformer { } // initializeEventListeners initializes the event listeners for the informer. -func (w *Watcher) initializeEventListeners(ctx context.Context) error { +func (w *Watcher) initializeEventListeners() error { slog.Debug("Watcher::initializeEventListeners") var err error handlers := cache.ResourceEventHandlerFuncs{ - AddFunc: w.buildEventHandlerForAdd(ctx), - DeleteFunc: w.buildEventHandlerForDelete(ctx), - UpdateFunc: w.buildEventHandlerForUpdate(ctx), + AddFunc: w.buildEventHandlerForAdd(), + DeleteFunc: w.buildEventHandlerForDelete(), + UpdateFunc: w.buildEventHandlerForUpdate(), } w.eventHandlerRegistration, err = w.informer.AddEventHandler(handlers) @@ -200,42 +180,3 @@ func (w *Watcher) initializeEventListeners(ctx context.Context) error { return nil } - -// notMasterNode retrieves the IP Addresses of the nodes in the cluster. Currently, the master node is excluded. This is -// because the master node may or may not be a worker node and thus may not be able to route traffic. -func (w *Watcher) retrieveNodeIps(ctx context.Context) ([]string, error) { - started := time.Now() - slog.Debug("Watcher::retrieveNodeIps") - - var nodeIps []string - - nodes, err := w.k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) - if err != nil { - slog.Error("error occurred retrieving the list of nodes", "error", err) - return nil, err - } - - for _, node := range nodes.Items { - // this is kind of a broad assumption, should probably make this a configurable option - if w.notMasterNode(node) { - for _, address := range node.Status.Addresses { - if address.Type == v1.NodeInternalIP { - nodeIps = append(nodeIps, address.Address) - } - } - } - } - - slog.Debug("Watcher::retrieveNodeIps duration", "duration", time.Since(started).Nanoseconds()) - - return nodeIps, nil -} - -// notMasterNode determines if the node is a master node. -func (w *Watcher) notMasterNode(node v1.Node) bool { - slog.Debug("Watcher::notMasterNode") - - _, found := node.Labels["node-role.kubernetes.io/master"] - - return !found -} diff --git a/internal/translation/translator.go b/internal/translation/translator.go index dceecf5..6d7928d 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -6,20 +6,32 @@ package translation import ( + "context" "fmt" "log/slog" "strings" + "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) +type Translator struct { + k8sClient kubernetes.Interface +} + +func NewTranslator(k8sClient kubernetes.Interface) *Translator { + return &Translator{k8sClient} +} + // Translate transforms event data into an intermediate format that can be consumed by the BorderClient implementations // and used to update the Border Servers. -func Translate(event *core.Event) (core.ServerUpdateEvents, error) { +func (t *Translator) Translate(ctx context.Context, event *core.Event) (core.ServerUpdateEvents, error) { slog.Debug("Translate::Translate") - return buildServerUpdateEvents(event.Service.Spec.Ports, event) + return t.buildServerUpdateEvents(ctx, event.Service.Spec.Ports, event) } // buildServerUpdateEvents builds a list of ServerUpdateEvents based on the event type @@ -28,18 +40,108 @@ func Translate(event *core.Event) (core.ServerUpdateEvents, error) { // and the list of servers in NGINX+. // The NGINX+ Client uses a single server for Deleted events; // so the list of servers is broken up into individual events. -func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.ServerUpdateEvents, error) { +func (t *Translator) buildServerUpdateEvents(ctx context.Context, ports []v1.ServicePort, event *core.Event, +) (events core.ServerUpdateEvents, err error) { slog.Debug("Translate::buildServerUpdateEvents", "ports", ports) + switch event.Service.Spec.Type { + case v1.ServiceTypeNodePort: + return t.buildNodeIPEvents(ctx, ports, event) + case v1.ServiceTypeClusterIP: + return t.buildClusterIPEvents(ctx, event) + default: + return events, fmt.Errorf("unsupported service type: %s", event.Service.Spec.Type) + } +} + +type upstream struct { + context string + name string +} + +func (t *Translator) buildClusterIPEvents(ctx context.Context, event *core.Event, +) (events core.ServerUpdateEvents, err error) { + namespace := event.Service.GetObjectMeta().GetNamespace() + serviceName := event.Service.Name + + logger := slog.With("namespace", namespace, "serviceName", serviceName) + logger.Debug("Translate::buildClusterIPEvents") + + if event.Type == core.Deleted { + for _, port := range event.Service.Spec.Ports { + context, upstreamName, pErr := getContextAndUpstreamName(port.Name) + if pErr != nil { + logger.Info(pErr.Error()) + continue + } + events = append(events, core.NewServerUpdateEvent(core.Updated, upstreamName, context, nil)) + } + return events, nil + } + + s := t.k8sClient.DiscoveryV1().EndpointSlices(namespace) + list, err := s.List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("kubernetes.io/service-name=%s", serviceName)}) + if err != nil { + logger.Error(`error occurred retrieving the list of endpoint slices`, "error", err) + return events, err + } + + upstreams := make(map[upstream][]*core.UpstreamServer) + + for _, endpointSlice := range list.Items { + for _, port := range endpointSlice.Ports { + if port.Name == nil || port.Port == nil { + continue + } + + context, upstreamName, err := getContextAndUpstreamName(*port.Name) + if err != nil { + logger.Info(err.Error()) + continue + } + + u := upstream{ + context: context, + name: upstreamName, + } + servers := upstreams[u] + + for _, endpoint := range endpointSlice.Endpoints { + for _, address := range endpoint.Addresses { + host := fmt.Sprintf("%s:%d", address, *port.Port) + servers = append(servers, core.NewUpstreamServer(host)) + } + } + + upstreams[u] = servers + } + } + + for u, servers := range upstreams { + events = append(events, core.NewServerUpdateEvent(core.Updated, u.name, u.context, servers)) + } + + return events, nil +} + +func (t *Translator) buildNodeIPEvents(ctx context.Context, ports []v1.ServicePort, event *core.Event, +) (core.ServerUpdateEvents, error) { + slog.Debug("Translate::buildNodeIPEvents", "ports", ports) + events := core.ServerUpdateEvents{} for _, port := range ports { - context, upstreamName, err := getContextAndUpstreamName(port) + context, upstreamName, err := getContextAndUpstreamName(port.Name) if err != nil { slog.Info(err.Error()) continue } - upstreamServers := buildUpstreamServers(event.NodeIps, port) + addresses, err := t.retrieveNodeIps(ctx) + if err != nil { + return nil, err + } + + upstreamServers := buildUpstreamServers(addresses, port) switch event.Type { case core.Created: @@ -49,14 +151,11 @@ func buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event) (core.Se events = append(events, core.NewServerUpdateEvent(event.Type, upstreamName, context, upstreamServers)) case core.Deleted: - for _, server := range upstreamServers { - events = append(events, core.NewServerUpdateEvent( - event.Type, upstreamName, context, core.UpstreamServers{server}, - )) - } - + events = append(events, core.NewServerUpdateEvent( + core.Updated, upstreamName, context, nil, + )) default: - slog.Warn(`Translator::buildServerUpdateEvents: unknown event type`, "type", event.Type) + slog.Warn(`Translator::buildNodeIPEvents: unknown event type`, "type", event.Type) } } @@ -78,15 +177,54 @@ func buildUpstreamServers(nodeIPs []string, port v1.ServicePort) core.UpstreamSe // getContextAndUpstreamName returns the nginx context being supplied by the port (either "http" or "stream") // and the upstream name. -func getContextAndUpstreamName(port v1.ServicePort) (clientType string, appName string, err error) { - context, upstreamName, found := strings.Cut(port.Name, "-") +func getContextAndUpstreamName(portName string) (clientType string, appName string, err error) { + context, upstreamName, found := strings.Cut(portName, "-") switch { case !found: return clientType, appName, - fmt.Errorf("ignoring port %s because it is not in the format [http|stream]-{upstreamName}", port.Name) + fmt.Errorf("ignoring port %s because it is not in the format [http|stream]-{upstreamName}", portName) case context != "http" && context != "stream": - return clientType, appName, fmt.Errorf("port name %s does not include \"http\" or \"stream\" context", port.Name) + return clientType, appName, fmt.Errorf("port name %s does not include \"http\" or \"stream\" context", portName) default: return context, upstreamName, nil } } + +// notMasterNode retrieves the IP Addresses of the nodes in the cluster. Currently, the master node is excluded. This is +// because the master node may or may not be a worker node and thus may not be able to route traffic. +func (t *Translator) retrieveNodeIps(ctx context.Context) ([]string, error) { + started := time.Now() + slog.Debug("Translator::retrieveNodeIps") + + var nodeIps []string + + nodes, err := t.k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + slog.Error("error occurred retrieving the list of nodes", "error", err) + return nil, err + } + + for _, node := range nodes.Items { + // this is kind of a broad assumption, should probably make this a configurable option + if notMasterNode(node) { + for _, address := range node.Status.Addresses { + if address.Type == v1.NodeInternalIP { + nodeIps = append(nodeIps, address.Address) + } + } + } + } + + slog.Debug("Translator::retrieveNodeIps duration", "duration", time.Since(started).Nanoseconds()) + + return nodeIps, nil +} + +// notMasterNode determines if the node is a master node. +func notMasterNode(node v1.Node) bool { + slog.Debug("Translator::notMasterNode") + + _, found := node.Labels["node-role.kubernetes.io/master"] + + return !found +} diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index 5b508c3..73c99a4 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -12,7 +12,12 @@ import ( "time" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" + "github.com/nginxinc/kubernetes-nginx-ingress/pkg/pointer" + "golang.org/x/net/context" v1 "k8s.io/api/core/v1" + discovery "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) const ( @@ -20,6 +25,9 @@ const ( ManyNodes = 7 NoNodes = 0 OneNode = 1 + ManyEndpointSlices = 7 + NoEndpointSlices = 0 + OneEndpointSlice = 1 TranslateErrorFormat = "Translate() error = %v" ) @@ -29,130 +37,266 @@ const ( func TestCreatedTranslateNoPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 0 + testcases := map[string]struct{ serviceType v1.ServiceType }{ + "nodePort": {v1.ServiceTypeNodePort}, + "clusterIP": {v1.ServiceTypeClusterIP}, + } - service := defaultService() - event := buildCreatedEvent(service, OneNode) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) - } + const expectedEventCount = 0 + + service := defaultService(tc.serviceType) + event := buildCreatedEvent(service) + + translator := NewTranslator(NewFakeClient([]discovery.EndpointSlice{}, []v1.Node{})) - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + }) } } func TestCreatedTranslateNoInterestingPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 1 + testcases := map[string]struct{ serviceType v1.ServiceType }{ + "nodePort": {v1.ServiceTypeNodePort}, + "clusterIP": {v1.ServiceTypeClusterIP}, + } - ports := generateUpdatablePorts(portCount, 0) - service := serviceWithPorts(ports) - event := buildCreatedEvent(service, OneNode) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) - } + const expectedEventCount = 0 + const portCount = 1 + + ports := generateUpdatablePorts(portCount, 0) + service := serviceWithPorts(tc.serviceType, ports) + event := buildCreatedEvent(service) + + translator := NewTranslator(NewFakeClient([]discovery.EndpointSlice{}, []v1.Node{})) - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + }) } } +//nolint:dupl func TestCreatedTranslateOneInterestingPort(t *testing.T) { t.Parallel() - const expectedEventCount = 1 - const portCount = 1 + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 1, 1), + expectedServerCount: OneEndpointSlice, + }, + } - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildCreatedEvent(service, OneNode) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) - } + const expectedEventCount = 1 + const portCount = 1 - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildCreatedEvent(service) - assertExpectedServerCount(t, OneNode, translatedEvents) + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, tc.expectedServerCount, translatedEvents) + }) + } } +//nolint:dupl func TestCreatedTranslateManyInterestingPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 4 - const portCount = 4 + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 4, 4), + expectedServerCount: OneEndpointSlice, + }, + } - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildCreatedEvent(service, OneNode) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) - } + const expectedEventCount = 4 + const portCount = 4 - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildCreatedEvent(service) - assertExpectedServerCount(t, OneNode, translatedEvents) + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, tc.expectedServerCount, translatedEvents) + }) + } } +//nolint:dupl func TestCreatedTranslateManyMixedPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 2 - const portCount = 6 - const updatablePortCount = 2 - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildCreatedEvent(service, OneNode) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 6, 2), + expectedServerCount: OneEndpointSlice, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + const expectedEventCount = 2 + const portCount = 6 + const updatablePortCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildCreatedEvent(service) - assertExpectedServerCount(t, OneNode, translatedEvents) + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, tc.expectedServerCount, translatedEvents) + }) + } } func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 2 - const portCount = 6 - const updatablePortCount = 2 - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildCreatedEvent(service, ManyNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + expectedServerCount: ManyNodes, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 6, 2), + expectedServerCount: ManyEndpointSlices, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 2 + const portCount = 6 + const updatablePortCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildCreatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, ManyNodes, translatedEvents) + }) } - - assertExpectedServerCount(t, ManyNodes, translatedEvents) } /* @@ -161,130 +305,289 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { func TestUpdatedTranslateNoPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - service := defaultService() - event := buildUpdatedEvent(service, OneNode) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 0, 0), + expectedServerCount: OneEndpointSlice, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 0 + + service := defaultService(tc.serviceType) + event := buildUpdatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + }) } } func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 1 - - ports := generateUpdatablePorts(portCount, 0) - service := serviceWithPorts(ports) - event := buildUpdatedEvent(service, OneNode) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 1, 0), + expectedServerCount: OneEndpointSlice, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 0 + const portCount = 1 + + ports := generateUpdatablePorts(portCount, 0) + service := serviceWithPorts(tc.serviceType, ports) + event := buildUpdatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + }) } } func TestUpdatedTranslateOneInterestingPort(t *testing.T) { t.Parallel() - const expectedEventCount = 1 - const portCount = 1 - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildUpdatedEvent(service, OneNode) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 1, 1), + expectedServerCount: OneEndpointSlice, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 1 + const portCount = 1 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildUpdatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, OneNode, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } +//nolint:dupl func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 4 - const portCount = 4 - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildUpdatedEvent(service, OneNode) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 4, 4), + expectedServerCount: OneEndpointSlice, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 4 + const portCount = 4 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildUpdatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, tc.expectedServerCount, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } +//nolint:dupl func TestUpdatedTranslateManyMixedPorts(t *testing.T) { t.Parallel() - const expectedEventCount = 2 - const portCount = 6 - const updatablePortCount = 2 - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildUpdatedEvent(service, OneNode) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + expectedServerCount: OneNode, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 6, 2), + expectedServerCount: OneEndpointSlice, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 2 + const portCount = 6 + const updatablePortCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildUpdatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, tc.expectedServerCount, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } +//nolint:dupl func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 2 - const portCount = 6 - const updatablePortCount = 2 - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildUpdatedEvent(service, ManyNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + expectedServerCount int + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + expectedServerCount: ManyNodes, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 6, 2), + expectedServerCount: ManyEndpointSlices, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 2 + const portCount = 6 + const updatablePortCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildUpdatedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, tc.expectedServerCount, translatedEvents) + }) } - - assertExpectedServerCount(t, ManyNodes, translatedEvents) } /* @@ -293,331 +596,682 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - service := defaultService() - event := buildDeletedEvent(service, NoNodes) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + const expectedEventCount = 0 + + service := defaultService(tc.serviceType) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } - assertExpectedServerCount(t, ManyNodes, translatedEvents) + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 1 - ports := generateUpdatablePorts(portCount, 0) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, NoNodes) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 0 + const portCount = 1 + + ports := generateUpdatablePorts(portCount, 0) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, ManyNodes, translatedEvents) } +//nolint:dupl func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 1 + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(0, 1, 1), + }, + } - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, NoNodes) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) - } + const expectedEventCount = 1 + const portCount = 1 - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } - assertExpectedServerCount(t, ManyNodes, translatedEvents) + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } +//nolint:dupl func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 4 - - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, NoNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(0, 4, 4), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 4 + const expectedEventCount = 4 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, ManyNodes, translatedEvents) } func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 6 - const updatablePortCount = 2 - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, NoNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(0, 6, 2), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + const portCount = 6 + const updatablePortCount = 2 + const expectedEventCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) - assertExpectedServerCount(t, ManyNodes, translatedEvents) + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } +//nolint:dupl func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - - service := defaultService() - event := buildDeletedEvent(service, OneNode) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 0, 0), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + const expectedEventCount = 0 + + service := defaultService(tc.serviceType) + event := buildDeletedEvent(service) - assertExpectedServerCount(t, ManyNodes, translatedEvents) + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - const portCount = 1 - - ports := generateUpdatablePorts(portCount, 0) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, OneNode) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 1, 0), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 1 + const expectedEventCount = 0 + + ports := generateUpdatablePorts(portCount, 0) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, ManyNodes, translatedEvents) } +//nolint:dupl func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { t.Parallel() - const expectedEventCount = 1 - const portCount = 1 - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, OneNode) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 1, 1), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + const portCount = 1 + const expectedEventCount = 1 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } - assertExpectedServerCount(t, OneNode, translatedEvents) + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } +//nolint:dupl func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { t.Parallel() - const expectedEventCount = 4 - const portCount = 4 - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, OneNode) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 4, 4), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + const portCount = 4 + const expectedEventCount = 4 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) - assertExpectedServerCount(t, OneNode, translatedEvents) + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { t.Parallel() - const expectedEventCount = 2 - const portCount = 6 - const updatablePortCount = 2 - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, OneNode) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(OneNode), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(OneEndpointSlice, 6, 2), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 6 + const updatablePortCount = 2 + const expectedEventCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } +//nolint:dupl func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { t.Parallel() - const expectedEventCount = 0 - service := defaultService() - event := buildDeletedEvent(service, ManyNodes) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 0, 0), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) - } + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const expectedEventCount = 0 + + service := defaultService(tc.serviceType) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } - assertExpectedServerCount(t, ManyNodes, translatedEvents) + assertExpectedServerCount(t, 0, translatedEvents) + }) + } } func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { t.Parallel() - const portCount = 1 - const updatablePortCount = 0 - const expectedEventCount = updatablePortCount * ManyNodes - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, ManyNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 1, 0), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 1 + const updatablePortCount = 0 + const expectedEventCount = updatablePortCount * ManyNodes + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, ManyNodes, translatedEvents) } +//nolint:dupl func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { t.Parallel() - const portCount = 1 - const expectedEventCount = portCount * ManyNodes - - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, ManyNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 1, 1), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 1 + const expectedEventCount = 1 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } +//nolint:dupl func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { t.Parallel() - const portCount = 4 - const expectedEventCount = portCount * ManyNodes - ports := generatePorts(portCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, ManyNodes) - - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 4, 4), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 4 + const expectedEventCount = 4 + + ports := generatePorts(portCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } func TestDeletedTranslateManyMixedPortsAndManyNodes(t *testing.T) { t.Parallel() - const portCount = 6 - const updatablePortCount = 2 - const expectedEventCount = updatablePortCount * ManyNodes - - ports := generateUpdatablePorts(portCount, updatablePortCount) - service := serviceWithPorts(ports) - event := buildDeletedEvent(service, ManyNodes) - translatedEvents, err := Translate(&event) - if err != nil { - t.Fatalf(TranslateErrorFormat, err) + testcases := map[string]struct { + serviceType v1.ServiceType + nodes []v1.Node + endpoints []discovery.EndpointSlice + }{ + "nodePort": { + serviceType: v1.ServiceTypeNodePort, + nodes: generateNodes(ManyNodes), + }, + "clusterIP": { + serviceType: v1.ServiceTypeClusterIP, + endpoints: generateEndpointSlices(ManyEndpointSlices, 6, 2), + }, } - actualEventCount := len(translatedEvents) - if actualEventCount != expectedEventCount { - t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + for name, tc := range testcases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + const portCount = 6 + const updatablePortCount = 2 + const expectedEventCount = 2 + + ports := generateUpdatablePorts(portCount, updatablePortCount) + service := serviceWithPorts(tc.serviceType, ports) + event := buildDeletedEvent(service) + + translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) + translatedEvents, err := translator.Translate(context.TODO(), &event) + if err != nil { + t.Fatalf(TranslateErrorFormat, err) + } + + actualEventCount := len(translatedEvents) + if actualEventCount != expectedEventCount { + t.Fatalf(AssertionFailureFormat, expectedEventCount, actualEventCount) + } + + assertExpectedServerCount(t, 0, translatedEvents) + }) } - - assertExpectedServerCount(t, OneNode, translatedEvents) } func assertExpectedServerCount(t *testing.T, expectedCount int, events core.ServerUpdateEvents) { @@ -629,46 +1283,98 @@ func assertExpectedServerCount(t *testing.T, expectedCount int, events core.Serv } } -func defaultService() *v1.Service { - return &v1.Service{} +func defaultService(serviceType v1.ServiceType) *v1.Service { + return &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-service", + Labels: map[string]string{"kubernetes.io/service-name": "default-service"}, + }, + Spec: v1.ServiceSpec{ + Type: serviceType, + }, + } } -func serviceWithPorts(ports []v1.ServicePort) *v1.Service { +func serviceWithPorts(serviceType v1.ServiceType, ports []v1.ServicePort) *v1.Service { return &v1.Service{ Spec: v1.ServiceSpec{ + Type: serviceType, Ports: ports, }, } } -func buildCreatedEvent(service *v1.Service, nodeCount int) core.Event { - return buildEvent(core.Created, service, nodeCount) +func buildCreatedEvent(service *v1.Service) core.Event { + return buildEvent(core.Created, service) } -func buildDeletedEvent(service *v1.Service, nodeCount int) core.Event { - return buildEvent(core.Deleted, service, nodeCount) +func buildDeletedEvent(service *v1.Service) core.Event { + return buildEvent(core.Deleted, service) } -func buildUpdatedEvent(service *v1.Service, nodeCount int) core.Event { - return buildEvent(core.Updated, service, nodeCount) +func buildUpdatedEvent(service *v1.Service) core.Event { + return buildEvent(core.Updated, service) } -func buildEvent(eventType core.EventType, service *v1.Service, nodeCount int) core.Event { - previousService := defaultService() +func buildEvent(eventType core.EventType, service *v1.Service) core.Event { + previousService := defaultService(service.Spec.Type) - nodeIps := generateNodeIps(nodeCount) + event := core.NewEvent(eventType, service, previousService) + event.Service.Name = "default-service" + return event +} - return core.NewEvent(eventType, service, previousService, nodeIps) +func generateNodes(count int) (nodes []v1.Node) { + for i := 0; i < count; i++ { + nodes = append(nodes, v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("node%d", i), + }, + Status: v1.NodeStatus{ + Addresses: []v1.NodeAddress{ + { + Type: v1.NodeInternalIP, + Address: fmt.Sprintf("10.0.0.%v", i), + }, + }, + }, + }) + } + + return nodes } -func generateNodeIps(count int) []string { - var nodeIps []string +func generateEndpointSlices(endpointCount, portCount, updatablePortCount int, +) (endpointSlices []discovery.EndpointSlice) { + servicePorts := generateUpdatablePorts(portCount, updatablePortCount) - for i := 0; i < count; i++ { - nodeIps = append(nodeIps, fmt.Sprintf("10.0.0.%v", i)) + ports := make([]discovery.EndpointPort, 0, len(servicePorts)) + for _, servicePort := range servicePorts { + ports = append(ports, discovery.EndpointPort{ + Name: pointer.To(servicePort.Name), + Port: pointer.To(int32(8080)), + }) + } + + var endpoints []discovery.Endpoint + for i := 0; i < endpointCount; i++ { + endpoints = append(endpoints, discovery.Endpoint{ + Addresses: []string{ + fmt.Sprintf("10.0.0.%v", i), + }, + }) } - return nodeIps + endpointSlices = append(endpointSlices, discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpointSlice", + Labels: map[string]string{"kubernetes.io/service-name": "default-service"}, + }, + Endpoints: endpoints, + Ports: ports, + }) + + return endpointSlices } func generatePorts(portCount int) []v1.ServicePort { @@ -708,3 +1414,14 @@ func generateUpdatablePorts(portCount int, updatableCount int) []v1.ServicePort return ports } + +func NewFakeClient(endpointSlices []discovery.EndpointSlice, nodes []v1.Node) *fake.Clientset { + return fake.NewSimpleClientset( + &discovery.EndpointSliceList{ + Items: endpointSlices, + }, + &v1.NodeList{ + Items: nodes, + }, + ) +} diff --git a/test/mocks/mock_handler.go b/test/mocks/mock_handler.go index b854db9..f144cea 100644 --- a/test/mocks/mock_handler.go +++ b/test/mocks/mock_handler.go @@ -5,23 +5,22 @@ package mocks -import "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" +import ( + "context" -type MockHandler struct { -} + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" +) -func (h *MockHandler) AddRateLimitedEvent(_ *core.Event) { +type MockHandler struct{} +func (h *MockHandler) AddRateLimitedEvent(_ *core.Event) { } func (h *MockHandler) Initialize() { - } -func (h *MockHandler) Run(_ <-chan struct{}) { - +func (h *MockHandler) Run(ctx context.Context) { } func (h *MockHandler) ShutDown() { - } From 53927cb0c9e6b7d268f43e39e3c47d55723ed723 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 7 Nov 2024 14:58:10 -0700 Subject: [PATCH 084/136] NLB-4468 Bumped version to 0.7.0 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index b616048..faef31a 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.6.2 +0.7.0 From 57ab4941bb466313e373f35b712427581174f9cb Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 8 Nov 2024 02:02:38 +0530 Subject: [PATCH 085/136] NLB-5868: Use same image tag as appVersion We have always aimed at keeping the chart and app versions the same, but we also set the image tag in the values file. Doing so forces the image tag to be set to what's in the values files instead of using the appVersion of the Chart. This leads to issues where a newer version of the chart has an older image tag. What we want instead is to use the appVersion of the Helm chart as the image tag and only use the image tag in the values file when it is set. --- charts/nlk/values.yaml | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index f5be244..851f67d 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -18,7 +18,7 @@ nlk: repository: nginx/nginxaas-loadbalancer-kubernetes pullPolicy: Always ## Overrides the image tag whose default is the chart appVersion. - tag: 0.4.0 + # tag: 0.4.0 imagePullSecrets: [] nameOverride: "" fullnameOverride: "" diff --git a/version b/version index faef31a..39e898a 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.7.0 +0.7.1 From 5b5689d01c583b8a55ed1517f12aa82f1813473a Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Mon, 11 Nov 2024 09:13:04 -0700 Subject: [PATCH 086/136] NLB-5872 NLK main routine uses a shared informer factory All NLK modules can now consume shared informer resources that are created from a single factory. In this commit the only consumer of this shared factory is the watcher, but in future the translator will make use of it as well. The pattern makes initializing shared resources easier (e.g. a single call to wait for sync, a single call to start the factory). It will also help to avoid circular dependencies as modules will not be dependent on each other to expose shared informer cache resources. I've followed patterns laid down by the kubernetes project's sample controller with respect to the watcher. The watcher constructor now adds event handler routines to its shared informer before the informers are started. This eliminates the need for a separate Initialize() routine. --- cmd/nginx-loadbalancer-kubernetes/main.go | 20 ++-- internal/observation/watcher.go | 110 ++++++++-------------- internal/observation/watcher_test.go | 17 ++-- 3 files changed, 61 insertions(+), 86 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 00be5d4..99240d3 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -17,6 +17,7 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" "github.com/nginxinc/kubernetes-nginx-ingress/internal/translation" "github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" + "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/util/workqueue" @@ -53,18 +54,25 @@ func run() error { return fmt.Errorf(`error initializing synchronizer: %w`, err) } + factory := informers.NewSharedInformerFactoryWithOptions( + k8sClient, settings.Watcher.ResyncPeriod, + ) + handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translation.NewTranslator(k8sClient)) - watcher, err := observation.NewWatcher(settings, handler, k8sClient) + watcher, err := observation.NewWatcher(settings, handler, factory.Core().V1().Services()) if err != nil { return fmt.Errorf(`error occurred creating a watcher: %w`, err) } - err = watcher.Initialize() - if err != nil { - return fmt.Errorf(`error occurred initializing the watcher: %w`, err) + factory.Start(ctx.Done()) + results := factory.WaitForCacheSync(ctx.Done()) + for name, success := range results { + if !success { + return fmt.Errorf(`error occurred waiting for cache sync for %s`, name) + } } go handler.Run(ctx) @@ -73,9 +81,9 @@ func run() error { probeServer := probation.NewHealthServer() probeServer.Start() - err = watcher.Watch(ctx) + err = watcher.Run(ctx) if err != nil { - return fmt.Errorf(`error occurred watching for events: %w`, err) + return fmt.Errorf(`error occurred running watcher: %w`, err) } <-ctx.Done() diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 21ab068..5a2905e 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -7,7 +7,6 @@ package observation import ( "context" - "errors" "fmt" "log/slog" @@ -15,8 +14,7 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" v1 "k8s.io/api/core/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" + coreinformers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/tools/cache" ) @@ -24,69 +22,53 @@ import ( // Particularly, Services in the namespace defined in the WatcherSettings::NginxIngressNamespace setting. // When a change is detected, an Event is generated and added to the Handler's queue. type Watcher struct { - // eventHandlerRegistration is used to track the event handlers - eventHandlerRegistration interface{} - // handler is the event handler handler HandlerInterface - // informer is the informer used to watch for changes to Kubernetes resources - informer cache.SharedIndexInformer - - k8sClient kubernetes.Interface - // settings is the configuration settings settings configuration.Settings + + // servicesInformer is the informer used to watch for changes to services + servicesInformer cache.SharedIndexInformer } // NewWatcher creates a new Watcher func NewWatcher( - settings configuration.Settings, handler HandlerInterface, k8sClient kubernetes.Interface, + settings configuration.Settings, + handler HandlerInterface, + serviceInformer coreinformers.ServiceInformer, ) (*Watcher, error) { - return &Watcher{ - handler: handler, - settings: settings, - k8sClient: k8sClient, - }, nil -} + if serviceInformer == nil { + return nil, fmt.Errorf("service informer cannot be nil") + } -// Initialize initializes the Watcher, must be called before Watch -func (w *Watcher) Initialize() error { - slog.Debug("Watcher::Initialize") - var err error + servicesInformer := serviceInformer.Informer() - w.informer = w.buildInformer() + w := &Watcher{ + handler: handler, + settings: settings, + servicesInformer: servicesInformer, + } - err = w.initializeEventListeners() - if err != nil { - return fmt.Errorf(`initialization error: %w`, err) + if err := w.initializeEventListeners(servicesInformer); err != nil { + return nil, err } - return nil + return w, nil } -// Watch starts the process of watching for changes to Kubernetes resources. +// Run starts the process of watching for changes to Kubernetes resources. // Initialize must be called before Watch. -func (w *Watcher) Watch(ctx context.Context) error { - slog.Debug("Watcher::Watch") - - if w.informer == nil { - return errors.New("error: Initialize must be called before Watch") +func (w *Watcher) Run(ctx context.Context) error { + if w.servicesInformer == nil { + return fmt.Errorf(`servicesInformer is nil`) } + slog.Debug("Watcher::Watch") + defer utilruntime.HandleCrash() defer w.handler.ShutDown() - go w.informer.Run(ctx.Done()) - - if !cache.WaitForNamedCacheSync( - w.settings.Handler.WorkQueueSettings.Name, - ctx.Done(), - w.informer.HasSynced, - ) { - return fmt.Errorf(`error occurred waiting for the cache to sync`) - } - <-ctx.Done() return nil } @@ -101,10 +83,10 @@ func (w *Watcher) isDesiredService(service *v1.Service) bool { return annotation == w.settings.Watcher.ServiceAnnotation } -// buildEventHandlerForAdd creates a function that is used as an event handler +// buildServiceEventHandlerForAdd creates a function that is used as an event handler // for the informer when Add events are raised. -func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { - slog.Info("Watcher::buildEventHandlerForAdd") +func (w *Watcher) buildServiceEventHandlerForAdd() func(interface{}) { + slog.Info("Watcher::buildServiceEventHandlerForAdd") return func(obj interface{}) { service := obj.(*v1.Service) if !w.isDesiredService(service) { @@ -117,10 +99,10 @@ func (w *Watcher) buildEventHandlerForAdd() func(interface{}) { } } -// buildEventHandlerForDelete creates a function that is used as an event handler +// buildServiceEventHandlerForDelete creates a function that is used as an event handler // for the informer when Delete events are raised. -func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { - slog.Info("Watcher::buildEventHandlerForDelete") +func (w *Watcher) buildServiceEventHandlerForDelete() func(interface{}) { + slog.Info("Watcher::buildServiceEventHandlerForDelete") return func(obj interface{}) { service := obj.(*v1.Service) if !w.isDesiredService(service) { @@ -133,10 +115,10 @@ func (w *Watcher) buildEventHandlerForDelete() func(interface{}) { } } -// buildEventHandlerForUpdate creates a function that is used as an event handler +// buildServiceEventHandlerForUpdate creates a function that is used as an event handler // for the informer when Update events are raised. -func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { - slog.Info("Watcher::buildEventHandlerForUpdate") +func (w *Watcher) buildServiceEventHandlerForUpdate() func(interface{}, interface{}) { + slog.Info("Watcher::buildServiceEventHandlerForUpdate") return func(previous, updated interface{}) { // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API service := updated.(*v1.Service) @@ -150,30 +132,20 @@ func (w *Watcher) buildEventHandlerForUpdate() func(interface{}, interface{}) { } } -// buildInformer creates the informer used to watch for changes to Kubernetes resources. -func (w *Watcher) buildInformer() cache.SharedIndexInformer { - slog.Debug("Watcher::buildInformer") - - factory := informers.NewSharedInformerFactoryWithOptions( - w.k8sClient, w.settings.Watcher.ResyncPeriod, - ) - informer := factory.Core().V1().Services().Informer() - - return informer -} - // initializeEventListeners initializes the event listeners for the informer. -func (w *Watcher) initializeEventListeners() error { +func (w *Watcher) initializeEventListeners( + servicesInformer cache.SharedIndexInformer, +) error { slog.Debug("Watcher::initializeEventListeners") var err error handlers := cache.ResourceEventHandlerFuncs{ - AddFunc: w.buildEventHandlerForAdd(), - DeleteFunc: w.buildEventHandlerForDelete(), - UpdateFunc: w.buildEventHandlerForUpdate(), + AddFunc: w.buildServiceEventHandlerForAdd(), + DeleteFunc: w.buildServiceEventHandlerForDelete(), + UpdateFunc: w.buildServiceEventHandlerForUpdate(), } - w.eventHandlerRegistration, err = w.informer.AddEventHandler(handlers) + _, err = servicesInformer.AddEventHandler(handlers) if err != nil { return fmt.Errorf(`error occurred adding event handlers: %w`, err) } diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index f8de849..6c78297 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -6,25 +6,20 @@ package observation import ( - "context" "testing" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" - "k8s.io/client-go/kubernetes" + "github.com/stretchr/testify/require" ) -func TestWatcher_MustInitialize(t *testing.T) { +func TestWatcher_ErrWithNilInformer(t *testing.T) { t.Parallel() - watcher, _ := buildWatcher() - if err := watcher.Watch(context.Background()); err == nil { - t.Errorf("Expected error, got %s", err) - } + _, err := buildWatcherWithNilInformer() + require.Error(t, err, "expected construction of watcher with nil informer to fail") } -func buildWatcher() (*Watcher, error) { - k8sClient := &kubernetes.Clientset{} +func buildWatcherWithNilInformer() (*Watcher, error) { handler := &mocks.MockHandler{} - - return NewWatcher(configuration.Settings{}, handler, k8sClient) + return NewWatcher(configuration.Settings{}, handler, nil) } From a8dd150b084fb47e42d48663bf8a2fc75975921a Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 13 Nov 2024 15:07:27 -0700 Subject: [PATCH 087/136] NLB-5872 Added event handlers for endpoint slice events Also added the register of services we care about --- cmd/nginx-loadbalancer-kubernetes/main.go | 5 +- internal/observation/register.go | 49 ++++++++++ internal/observation/watcher.go | 107 ++++++++++++++++++++-- internal/observation/watcher_test.go | 2 +- 4 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 internal/observation/register.go diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 99240d3..21c609c 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -58,11 +58,14 @@ func run() error { k8sClient, settings.Watcher.ResyncPeriod, ) + serviceInformer := factory.Core().V1().Services() + endpointSliceInformer := factory.Discovery().V1().EndpointSlices() + handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translation.NewTranslator(k8sClient)) - watcher, err := observation.NewWatcher(settings, handler, factory.Core().V1().Services()) + watcher, err := observation.NewWatcher(settings, handler, serviceInformer, endpointSliceInformer) if err != nil { return fmt.Errorf(`error occurred creating a watcher: %w`, err) } diff --git a/internal/observation/register.go b/internal/observation/register.go new file mode 100644 index 0000000..26ab86d --- /dev/null +++ b/internal/observation/register.go @@ -0,0 +1,49 @@ +package observation + +import ( + "sync" + + v1 "k8s.io/api/core/v1" +) + +// register holds references to the services that the user has configured for use with NLK +type register struct { + mu sync.RWMutex // protects register + services map[registerKey]*v1.Service +} + +type registerKey struct { + serviceName string + namespace string +} + +func newRegister() *register { + return ®ister{ + services: make(map[registerKey]*v1.Service), + } +} + +// addOrUpdateService adds the service to the register if not found, else updates the existing service +func (r *register) addOrUpdateService(service *v1.Service) { + r.mu.Lock() + defer r.mu.Unlock() + + r.services[registerKey{namespace: service.Namespace, serviceName: service.Name}] = service +} + +// removeService removes the service from the register +func (r *register) removeService(service *v1.Service) { + r.mu.Lock() + defer r.mu.Unlock() + + delete(r.services, registerKey{namespace: service.Namespace, serviceName: service.Name}) +} + +// getService returns the service from the register if found +func (r *register) getService(namespace string, serviceName string) (*v1.Service, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + s, ok := r.services[registerKey{namespace: namespace, serviceName: serviceName}] + return s, ok +} diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 5a2905e..9752b38 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -13,8 +13,10 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" v1 "k8s.io/api/core/v1" + discovery "k8s.io/api/discovery/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" coreinformers "k8s.io/client-go/informers/core/v1" + discoveryinformers "k8s.io/client-go/informers/discovery/v1" "k8s.io/client-go/tools/cache" ) @@ -30,6 +32,11 @@ type Watcher struct { // servicesInformer is the informer used to watch for changes to services servicesInformer cache.SharedIndexInformer + + // endpointSliceInformer is the informer used to watch for changes to endpoint slices + endpointSliceInformer cache.SharedIndexInformer + + register *register } // NewWatcher creates a new Watcher @@ -37,17 +44,25 @@ func NewWatcher( settings configuration.Settings, handler HandlerInterface, serviceInformer coreinformers.ServiceInformer, + endpointSliceInformer discoveryinformers.EndpointSliceInformer, ) (*Watcher, error) { if serviceInformer == nil { return nil, fmt.Errorf("service informer cannot be nil") } + if endpointSliceInformer == nil { + return nil, fmt.Errorf("endpoint slice informer cannot be nil") + } + servicesInformer := serviceInformer.Informer() + endpointSlicesInformer := endpointSliceInformer.Informer() w := &Watcher{ - handler: handler, - settings: settings, - servicesInformer: servicesInformer, + handler: handler, + settings: settings, + servicesInformer: servicesInformer, + endpointSliceInformer: endpointSlicesInformer, + register: newRegister(), } if err := w.initializeEventListeners(servicesInformer); err != nil { @@ -83,6 +98,69 @@ func (w *Watcher) isDesiredService(service *v1.Service) bool { return annotation == w.settings.Watcher.ServiceAnnotation } +func (w *Watcher) buildEndpointSlicesEventHandlerForAdd() func(interface{}) { + slog.Info("Watcher::buildEndpointSlicesEventHandlerForAdd") + return func(obj interface{}) { + endpointSlice, ok := obj.(*discovery.EndpointSlice) + if !ok { + slog.Error("could not convert event object to EndpointSlice", "obj", obj) + return + } + + service, ok := w.register.getService(endpointSlice.Namespace, endpointSlice.Labels["kubernetes.io/service-name"]) + if !ok { + // not interested in any unregistered service + return + } + + var previousService *v1.Service + e := core.NewEvent(core.Updated, service, previousService) + w.handler.AddRateLimitedEvent(&e) + } +} + +func (w *Watcher) buildEndpointSlicesEventHandlerForUpdate() func(interface{}, interface{}) { + slog.Info("Watcher::buildEndpointSlicesEventHandlerForUpdate") + return func(previous, updated interface{}) { + endpointSlice, ok := updated.(*discovery.EndpointSlice) + if !ok { + slog.Error("could not convert event object to EndpointSlice", "obj", updated) + return + } + + service, ok := w.register.getService(endpointSlice.Namespace, endpointSlice.Labels["kubernetes.io/service-name"]) + if !ok { + // not interested in any unregistered service + return + } + + var previousService *v1.Service + e := core.NewEvent(core.Updated, service, previousService) + w.handler.AddRateLimitedEvent(&e) + } +} + +func (w *Watcher) buildEndpointSlicesEventHandlerForDelete() func(interface{}) { + slog.Info("Watcher::buildEndpointSlicesEventHandlerForDelete") + return func(obj interface{}) { + endpointSlice, ok := obj.(*discovery.EndpointSlice) + if !ok { + slog.Error("could not convert event object to EndpointSlice", "obj", obj) + return + } + + service, ok := w.register.getService(endpointSlice.Namespace, endpointSlice.Labels["kubernetes.io/service-name"]) + if !ok { + // not interested in any unregistered service + return + } + + var previousService *v1.Service + e := core.NewEvent(core.Deleted, service, previousService) + w.handler.AddRateLimitedEvent(&e) + } +} + // buildServiceEventHandlerForAdd creates a function that is used as an event handler // for the informer when Add events are raised. func (w *Watcher) buildServiceEventHandlerForAdd() func(interface{}) { @@ -93,6 +171,8 @@ func (w *Watcher) buildServiceEventHandlerForAdd() func(interface{}) { return } + w.register.addOrUpdateService(service) + var previousService *v1.Service e := core.NewEvent(core.Created, service, previousService) w.handler.AddRateLimitedEvent(&e) @@ -109,6 +189,8 @@ func (w *Watcher) buildServiceEventHandlerForDelete() func(interface{}) { return } + w.register.removeService(service) + var previousService *v1.Service e := core.NewEvent(core.Deleted, service, previousService) w.handler.AddRateLimitedEvent(&e) @@ -126,6 +208,8 @@ func (w *Watcher) buildServiceEventHandlerForUpdate() func(interface{}, interfac return } + w.register.addOrUpdateService(service) + previousService := previous.(*v1.Service) e := core.NewEvent(core.Updated, service, previousService) w.handler.AddRateLimitedEvent(&e) @@ -139,15 +223,26 @@ func (w *Watcher) initializeEventListeners( slog.Debug("Watcher::initializeEventListeners") var err error - handlers := cache.ResourceEventHandlerFuncs{ + serviceHandlers := cache.ResourceEventHandlerFuncs{ AddFunc: w.buildServiceEventHandlerForAdd(), DeleteFunc: w.buildServiceEventHandlerForDelete(), UpdateFunc: w.buildServiceEventHandlerForUpdate(), } - _, err = servicesInformer.AddEventHandler(handlers) + endpointSliceHandlers := cache.ResourceEventHandlerFuncs{ + AddFunc: w.buildEndpointSlicesEventHandlerForAdd(), + DeleteFunc: w.buildEndpointSlicesEventHandlerForDelete(), + UpdateFunc: w.buildEndpointSlicesEventHandlerForUpdate(), + } + + _, err = servicesInformer.AddEventHandler(serviceHandlers) + if err != nil { + return fmt.Errorf(`error occurred adding service event handlers: %w`, err) + } + + _, err = w.endpointSliceInformer.AddEventHandler(endpointSliceHandlers) if err != nil { - return fmt.Errorf(`error occurred adding event handlers: %w`, err) + return fmt.Errorf(`error occurred adding endpoint slice event handlers: %w`, err) } return nil diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index 6c78297..b39a989 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -21,5 +21,5 @@ func TestWatcher_ErrWithNilInformer(t *testing.T) { func buildWatcherWithNilInformer() (*Watcher, error) { handler := &mocks.MockHandler{} - return NewWatcher(configuration.Settings{}, handler, nil) + return NewWatcher(configuration.Settings{}, handler, nil, nil) } From 22d2d201a55b611b34cb9a6e429a73c3df3135a5 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 14 Nov 2024 09:44:58 -0700 Subject: [PATCH 088/136] NLB-5872 Added node informer to watcher Any node event from the k8s cluster will cause a service event for every registered service to be added to the handler's rate-limited work queue. --- cmd/nginx-loadbalancer-kubernetes/main.go | 3 +- internal/observation/register.go | 14 ++++++ internal/observation/watcher.go | 60 +++++++++++++++++++++++ internal/observation/watcher_test.go | 4 +- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 21c609c..21f3e09 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -60,12 +60,13 @@ func run() error { serviceInformer := factory.Core().V1().Services() endpointSliceInformer := factory.Discovery().V1().EndpointSlices() + nodesInformer := factory.Core().V1().Nodes() handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translation.NewTranslator(k8sClient)) - watcher, err := observation.NewWatcher(settings, handler, serviceInformer, endpointSliceInformer) + watcher, err := observation.NewWatcher(settings, handler, serviceInformer, endpointSliceInformer, nodesInformer) if err != nil { return fmt.Errorf(`error occurred creating a watcher: %w`, err) } diff --git a/internal/observation/register.go b/internal/observation/register.go index 26ab86d..bfe61f8 100644 --- a/internal/observation/register.go +++ b/internal/observation/register.go @@ -47,3 +47,17 @@ func (r *register) getService(namespace string, serviceName string) (*v1.Service s, ok := r.services[registerKey{namespace: namespace, serviceName: serviceName}] return s, ok } + +// listServices returns all the services in the register +func (r *register) listServices() []*v1.Service { + r.mu.RLock() + defer r.mu.RUnlock() + + services := make([]*v1.Service, 0, len(r.services)) + + for _, service := range r.services { + services = append(services, service) + } + + return services +} diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 9752b38..21d6e02 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -36,6 +36,9 @@ type Watcher struct { // endpointSliceInformer is the informer used to watch for changes to endpoint slices endpointSliceInformer cache.SharedIndexInformer + // nodesInformer is the informer used to watch for changes to nodes + nodesInformer cache.SharedIndexInformer + register *register } @@ -45,6 +48,7 @@ func NewWatcher( handler HandlerInterface, serviceInformer coreinformers.ServiceInformer, endpointSliceInformer discoveryinformers.EndpointSliceInformer, + nodeInformer coreinformers.NodeInformer, ) (*Watcher, error) { if serviceInformer == nil { return nil, fmt.Errorf("service informer cannot be nil") @@ -54,14 +58,20 @@ func NewWatcher( return nil, fmt.Errorf("endpoint slice informer cannot be nil") } + if nodeInformer == nil { + return nil, fmt.Errorf("node informer cannot be nil") + } + servicesInformer := serviceInformer.Informer() endpointSlicesInformer := endpointSliceInformer.Informer() + nodesInformer := nodeInformer.Informer() w := &Watcher{ handler: handler, settings: settings, servicesInformer: servicesInformer, endpointSliceInformer: endpointSlicesInformer, + nodesInformer: nodesInformer, register: newRegister(), } @@ -98,9 +108,46 @@ func (w *Watcher) isDesiredService(service *v1.Service) bool { return annotation == w.settings.Watcher.ServiceAnnotation } +func (w *Watcher) buildNodesEventHandlerForAdd() func(interface{}) { + slog.Info("Watcher::buildNodesEventHandlerForAdd") + return func(obj interface{}) { + slog.Debug("received node add event") + for _, service := range w.register.listServices() { + var previousService *v1.Service + e := core.NewEvent(core.Updated, service, previousService) + w.handler.AddRateLimitedEvent(&e) + } + } +} + +func (w *Watcher) buildNodesEventHandlerForUpdate() func(interface{}, interface{}) { + slog.Info("Watcher::buildNodesEventHandlerForUpdate") + return func(previous, updated interface{}) { + slog.Debug("received node update event") + for _, service := range w.register.listServices() { + var previousService *v1.Service + e := core.NewEvent(core.Updated, service, previousService) + w.handler.AddRateLimitedEvent(&e) + } + } +} + +func (w *Watcher) buildNodesEventHandlerForDelete() func(interface{}) { + slog.Info("Watcher::buildNodesEventHandlerForDelete") + return func(obj interface{}) { + slog.Debug("received node delete event") + for _, service := range w.register.listServices() { + var previousService *v1.Service + e := core.NewEvent(core.Updated, service, previousService) + w.handler.AddRateLimitedEvent(&e) + } + } +} + func (w *Watcher) buildEndpointSlicesEventHandlerForAdd() func(interface{}) { slog.Info("Watcher::buildEndpointSlicesEventHandlerForAdd") return func(obj interface{}) { + slog.Debug("received endpoint slice add event") endpointSlice, ok := obj.(*discovery.EndpointSlice) if !ok { slog.Error("could not convert event object to EndpointSlice", "obj", obj) @@ -122,6 +169,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForAdd() func(interface{}) { func (w *Watcher) buildEndpointSlicesEventHandlerForUpdate() func(interface{}, interface{}) { slog.Info("Watcher::buildEndpointSlicesEventHandlerForUpdate") return func(previous, updated interface{}) { + slog.Debug("received endpoint slice update event") endpointSlice, ok := updated.(*discovery.EndpointSlice) if !ok { slog.Error("could not convert event object to EndpointSlice", "obj", updated) @@ -143,6 +191,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForUpdate() func(interface{}, i func (w *Watcher) buildEndpointSlicesEventHandlerForDelete() func(interface{}) { slog.Info("Watcher::buildEndpointSlicesEventHandlerForDelete") return func(obj interface{}) { + slog.Debug("received endpoint slice delete event") endpointSlice, ok := obj.(*discovery.EndpointSlice) if !ok { slog.Error("could not convert event object to EndpointSlice", "obj", obj) @@ -235,6 +284,12 @@ func (w *Watcher) initializeEventListeners( UpdateFunc: w.buildEndpointSlicesEventHandlerForUpdate(), } + nodeHandlers := cache.ResourceEventHandlerFuncs{ + AddFunc: w.buildNodesEventHandlerForAdd(), + DeleteFunc: w.buildNodesEventHandlerForDelete(), + UpdateFunc: w.buildNodesEventHandlerForUpdate(), + } + _, err = servicesInformer.AddEventHandler(serviceHandlers) if err != nil { return fmt.Errorf(`error occurred adding service event handlers: %w`, err) @@ -245,5 +300,10 @@ func (w *Watcher) initializeEventListeners( return fmt.Errorf(`error occurred adding endpoint slice event handlers: %w`, err) } + _, err = w.nodesInformer.AddEventHandler(nodeHandlers) + if err != nil { + return fmt.Errorf(`error occurred adding node event handlers: %w`, err) + } + return nil } diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index b39a989..4618408 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestWatcher_ErrWithNilInformer(t *testing.T) { +func TestWatcher_ErrWithNilInformers(t *testing.T) { t.Parallel() _, err := buildWatcherWithNilInformer() require.Error(t, err, "expected construction of watcher with nil informer to fail") @@ -21,5 +21,5 @@ func TestWatcher_ErrWithNilInformer(t *testing.T) { func buildWatcherWithNilInformer() (*Watcher, error) { handler := &mocks.MockHandler{} - return NewWatcher(configuration.Settings{}, handler, nil, nil) + return NewWatcher(configuration.Settings{}, handler, nil, nil, nil) } From 4eb897c3a32042819c296ed491c2a454ba1a3123 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 15 Nov 2024 10:03:12 -0700 Subject: [PATCH 089/136] NLB-5872 Translator now uses shared listers to access endpoint slices and node ports This will save us from redundant kubernetes API calls. --- cmd/nginx-loadbalancer-kubernetes/main.go | 5 +- go.mod | 2 +- internal/observation/handler.go | 12 +- internal/observation/handler_test.go | 5 +- internal/translation/translator.go | 61 +++-- internal/translation/translator_test.go | 279 +++++++++++++--------- 6 files changed, 212 insertions(+), 152 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 21f3e09..b7381f0 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -60,11 +60,14 @@ func run() error { serviceInformer := factory.Core().V1().Services() endpointSliceInformer := factory.Discovery().V1().EndpointSlices() + endpointSliceLister := endpointSliceInformer.Lister() nodesInformer := factory.Core().V1().Nodes() + nodesLister := nodesInformer.Lister() handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) - handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translation.NewTranslator(k8sClient)) + translator := translation.NewTranslator(endpointSliceLister, nodesLister) + handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translator) watcher, err := observation.NewWatcher(settings, handler, serviceInformer, endpointSliceInformer, nodesInformer) if err != nil { diff --git a/go.mod b/go.mod index 6b26183..94a1319 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/nginxinc/nginx-plus-go-client v1.2.2 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - golang.org/x/net v0.23.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 @@ -55,6 +54,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect diff --git a/internal/observation/handler.go b/internal/observation/handler.go index 2b5bcfb..45ae6c4 100644 --- a/internal/observation/handler.go +++ b/internal/observation/handler.go @@ -47,7 +47,7 @@ type Handler struct { } type Translator interface { - Translate(context.Context, *core.Event) (core.ServerUpdateEvents, error) + Translate(*core.Event) (core.ServerUpdateEvents, error) } // NewHandler creates a new event handler @@ -76,7 +76,7 @@ func (h *Handler) Run(ctx context.Context) { slog.Debug("Handler::Run") worker := func() { - for h.handleNextEvent(ctx) { + for h.handleNextEvent() { // TODO: Add Telemetry } } @@ -95,11 +95,11 @@ func (h *Handler) ShutDown() { } // handleEvent feeds translated events to the synchronizer -func (h *Handler) handleEvent(ctx context.Context, e *core.Event) error { +func (h *Handler) handleEvent(e *core.Event) error { slog.Debug("Handler::handleEvent", "event", e) // TODO: Add Telemetry - events, err := h.translator.Translate(ctx, e) + events, err := h.translator.Translate(e) if err != nil { return fmt.Errorf(`Handler::handleEvent error translating: %v`, err) } @@ -110,7 +110,7 @@ func (h *Handler) handleEvent(ctx context.Context, e *core.Event) error { } // handleNextEvent pulls an event from the event queue and feeds it to the event handler with retry logic -func (h *Handler) handleNextEvent(ctx context.Context) bool { +func (h *Handler) handleNextEvent() bool { evt, quit := h.eventQueue.Get() slog.Debug("Handler::handleNextEvent", "event", evt, "quit", quit) if quit { @@ -120,7 +120,7 @@ func (h *Handler) handleNextEvent(ctx context.Context) bool { defer h.eventQueue.Done(evt) event := evt.(*core.Event) - h.withRetry(h.handleEvent(ctx, event), event) + h.withRetry(h.handleEvent(event), event) return true } diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go index 9dd736c..550b318 100644 --- a/internal/observation/handler_test.go +++ b/internal/observation/handler_test.go @@ -6,7 +6,6 @@ package observation import ( - "context" "testing" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" @@ -34,7 +33,7 @@ func TestHandler_AddsEventToSynchronizer(t *testing.T) { handler.AddRateLimitedEvent(event) - handler.handleNextEvent(context.Background()) + handler.handleNextEvent() if len(synchronizer.Events) != 1 { t.Errorf(`handler.AddRateLimitedEvent did not add the event to the queue`) @@ -54,6 +53,6 @@ func buildHandler() ( type fakeTranslator struct{} -func (t *fakeTranslator) Translate(ctx context.Context, event *core.Event) (core.ServerUpdateEvents, error) { +func (t *fakeTranslator) Translate(event *core.Event) (core.ServerUpdateEvents, error) { return core.ServerUpdateEvents{{}}, nil } diff --git a/internal/translation/translator.go b/internal/translation/translator.go index 6d7928d..117c0b9 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -6,7 +6,6 @@ package translation import ( - "context" "fmt" "log/slog" "strings" @@ -14,24 +13,32 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/labels" + corelisters "k8s.io/client-go/listers/core/v1" + discoverylisters "k8s.io/client-go/listers/discovery/v1" ) type Translator struct { - k8sClient kubernetes.Interface + endpointSliceLister discoverylisters.EndpointSliceLister + nodeLister corelisters.NodeLister } -func NewTranslator(k8sClient kubernetes.Interface) *Translator { - return &Translator{k8sClient} +func NewTranslator( + endpointSliceLister discoverylisters.EndpointSliceLister, + nodeLister corelisters.NodeLister, +) *Translator { + return &Translator{ + endpointSliceLister: endpointSliceLister, + nodeLister: nodeLister, + } } // Translate transforms event data into an intermediate format that can be consumed by the BorderClient implementations // and used to update the Border Servers. -func (t *Translator) Translate(ctx context.Context, event *core.Event) (core.ServerUpdateEvents, error) { +func (t *Translator) Translate(event *core.Event) (core.ServerUpdateEvents, error) { slog.Debug("Translate::Translate") - return t.buildServerUpdateEvents(ctx, event.Service.Spec.Ports, event) + return t.buildServerUpdateEvents(event.Service.Spec.Ports, event) } // buildServerUpdateEvents builds a list of ServerUpdateEvents based on the event type @@ -40,15 +47,15 @@ func (t *Translator) Translate(ctx context.Context, event *core.Event) (core.Ser // and the list of servers in NGINX+. // The NGINX+ Client uses a single server for Deleted events; // so the list of servers is broken up into individual events. -func (t *Translator) buildServerUpdateEvents(ctx context.Context, ports []v1.ServicePort, event *core.Event, +func (t *Translator) buildServerUpdateEvents(ports []v1.ServicePort, event *core.Event, ) (events core.ServerUpdateEvents, err error) { slog.Debug("Translate::buildServerUpdateEvents", "ports", ports) switch event.Service.Spec.Type { case v1.ServiceTypeNodePort: - return t.buildNodeIPEvents(ctx, ports, event) + return t.buildNodeIPEvents(ports, event) case v1.ServiceTypeClusterIP: - return t.buildClusterIPEvents(ctx, event) + return t.buildClusterIPEvents(event) default: return events, fmt.Errorf("unsupported service type: %s", event.Service.Spec.Type) } @@ -59,8 +66,7 @@ type upstream struct { name string } -func (t *Translator) buildClusterIPEvents(ctx context.Context, event *core.Event, -) (events core.ServerUpdateEvents, err error) { +func (t *Translator) buildClusterIPEvents(event *core.Event) (events core.ServerUpdateEvents, err error) { namespace := event.Service.GetObjectMeta().GetNamespace() serviceName := event.Service.Name @@ -79,8 +85,14 @@ func (t *Translator) buildClusterIPEvents(ctx context.Context, event *core.Event return events, nil } - s := t.k8sClient.DiscoveryV1().EndpointSlices(namespace) - list, err := s.List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("kubernetes.io/service-name=%s", serviceName)}) + lister := t.endpointSliceLister.EndpointSlices(namespace) + selector, err := labels.Parse(fmt.Sprintf("kubernetes.io/service-name=%s", serviceName)) + if err != nil { + logger.Error(`error occurred parsing the selector`, "error", err) + return events, err + } + + list, err := lister.List(selector) if err != nil { logger.Error(`error occurred retrieving the list of endpoint slices`, "error", err) return events, err @@ -88,7 +100,7 @@ func (t *Translator) buildClusterIPEvents(ctx context.Context, event *core.Event upstreams := make(map[upstream][]*core.UpstreamServer) - for _, endpointSlice := range list.Items { + for _, endpointSlice := range list { for _, port := range endpointSlice.Ports { if port.Name == nil || port.Port == nil { continue @@ -124,7 +136,7 @@ func (t *Translator) buildClusterIPEvents(ctx context.Context, event *core.Event return events, nil } -func (t *Translator) buildNodeIPEvents(ctx context.Context, ports []v1.ServicePort, event *core.Event, +func (t *Translator) buildNodeIPEvents(ports []v1.ServicePort, event *core.Event, ) (core.ServerUpdateEvents, error) { slog.Debug("Translate::buildNodeIPEvents", "ports", ports) @@ -136,7 +148,7 @@ func (t *Translator) buildNodeIPEvents(ctx context.Context, ports []v1.ServicePo continue } - addresses, err := t.retrieveNodeIps(ctx) + addresses, err := t.retrieveNodeIps() if err != nil { return nil, err } @@ -192,21 +204,26 @@ func getContextAndUpstreamName(portName string) (clientType string, appName stri // notMasterNode retrieves the IP Addresses of the nodes in the cluster. Currently, the master node is excluded. This is // because the master node may or may not be a worker node and thus may not be able to route traffic. -func (t *Translator) retrieveNodeIps(ctx context.Context) ([]string, error) { +func (t *Translator) retrieveNodeIps() ([]string, error) { started := time.Now() slog.Debug("Translator::retrieveNodeIps") var nodeIps []string - nodes, err := t.k8sClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + nodes, err := t.nodeLister.List(labels.Everything()) if err != nil { slog.Error("error occurred retrieving the list of nodes", "error", err) return nil, err } - for _, node := range nodes.Items { + for _, node := range nodes { + if node == nil { + slog.Error("list contains nil node") + continue + } + // this is kind of a broad assumption, should probably make this a configurable option - if notMasterNode(node) { + if notMasterNode(*node) { for _, address := range node.Status.Addresses { if address.Type == v1.NodeInternalIP { nodeIps = append(nodeIps, address.Address) diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index 73c99a4..c6d42f5 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -13,11 +13,12 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/pkg/pointer" - "golang.org/x/net/context" v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" + "k8s.io/apimachinery/pkg/labels" + corelisters "k8s.io/client-go/listers/core/v1" + discoverylisters "k8s.io/client-go/listers/discovery/v1" ) const ( @@ -52,9 +53,12 @@ func TestCreatedTranslateNoPorts(t *testing.T) { service := defaultService(tc.serviceType) event := buildCreatedEvent(service) - translator := NewTranslator(NewFakeClient([]discovery.EndpointSlice{}, []v1.Node{})) + translator := NewTranslator( + NewFakeEndpointSliceLister([]*discovery.EndpointSlice{}, nil), + NewFakeNodeLister([]*v1.Node{}, nil), + ) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -86,9 +90,12 @@ func TestCreatedTranslateNoInterestingPorts(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildCreatedEvent(service) - translator := NewTranslator(NewFakeClient([]discovery.EndpointSlice{}, []v1.Node{})) + translator := NewTranslator( + NewFakeEndpointSliceLister([]*discovery.EndpointSlice{}, nil), + NewFakeNodeLister([]*v1.Node{}, nil), + ) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -106,8 +113,8 @@ func TestCreatedTranslateOneInterestingPort(t *testing.T) { t.Parallel() testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -134,8 +141,8 @@ func TestCreatedTranslateOneInterestingPort(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildCreatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -155,8 +162,8 @@ func TestCreatedTranslateManyInterestingPorts(t *testing.T) { t.Parallel() testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -183,8 +190,8 @@ func TestCreatedTranslateManyInterestingPorts(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildCreatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -205,8 +212,8 @@ func TestCreatedTranslateManyMixedPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -234,8 +241,8 @@ func TestCreatedTranslateManyMixedPorts(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildCreatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -255,8 +262,8 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -283,8 +290,8 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildCreatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -308,8 +315,8 @@ func TestUpdatedTranslateNoPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -333,8 +340,8 @@ func TestUpdatedTranslateNoPorts(t *testing.T) { service := defaultService(tc.serviceType) event := buildUpdatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -352,8 +359,8 @@ func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -379,8 +386,8 @@ func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildUpdatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -398,8 +405,8 @@ func TestUpdatedTranslateOneInterestingPort(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -425,8 +432,8 @@ func TestUpdatedTranslateOneInterestingPort(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildUpdatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -447,8 +454,8 @@ func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -474,8 +481,8 @@ func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildUpdatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -496,8 +503,8 @@ func TestUpdatedTranslateManyMixedPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -524,8 +531,8 @@ func TestUpdatedTranslateManyMixedPorts(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildUpdatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -546,8 +553,8 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice expectedServerCount int }{ "nodePort": { @@ -574,8 +581,8 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildUpdatedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -599,8 +606,8 @@ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -620,8 +627,8 @@ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { service := defaultService(tc.serviceType) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -641,8 +648,8 @@ func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -663,8 +670,8 @@ func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -685,8 +692,8 @@ func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -709,8 +716,8 @@ func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -731,8 +738,8 @@ func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -754,8 +761,8 @@ func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -775,8 +782,8 @@ func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -800,8 +807,8 @@ func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -822,8 +829,8 @@ func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -845,8 +852,8 @@ func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { service := defaultService(tc.serviceType) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -866,8 +873,8 @@ func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -890,8 +897,8 @@ func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -912,8 +919,8 @@ func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -937,8 +944,8 @@ func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -959,8 +966,8 @@ func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -984,8 +991,8 @@ func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1005,8 +1012,8 @@ func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1030,8 +1037,8 @@ func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1052,8 +1059,8 @@ func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1074,8 +1081,8 @@ func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { service := defaultService(tc.serviceType) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1095,8 +1102,8 @@ func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1120,8 +1127,8 @@ func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1142,8 +1149,8 @@ func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1166,8 +1173,8 @@ func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1188,8 +1195,8 @@ func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1212,8 +1219,8 @@ func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1233,8 +1240,8 @@ func TestDeletedTranslateManyMixedPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType - nodes []v1.Node - endpoints []discovery.EndpointSlice + nodes []*v1.Node + endpoints []*discovery.EndpointSlice }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1258,8 +1265,8 @@ func TestDeletedTranslateManyMixedPortsAndManyNodes(t *testing.T) { service := serviceWithPorts(tc.serviceType, ports) event := buildDeletedEvent(service) - translator := NewTranslator(NewFakeClient(tc.endpoints, tc.nodes)) - translatedEvents, err := translator.Translate(context.TODO(), &event) + translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) + translatedEvents, err := translator.Translate(&event) if err != nil { t.Fatalf(TranslateErrorFormat, err) } @@ -1324,9 +1331,9 @@ func buildEvent(eventType core.EventType, service *v1.Service) core.Event { return event } -func generateNodes(count int) (nodes []v1.Node) { +func generateNodes(count int) (nodes []*v1.Node) { for i := 0; i < count; i++ { - nodes = append(nodes, v1.Node{ + nodes = append(nodes, &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("node%d", i), }, @@ -1345,7 +1352,7 @@ func generateNodes(count int) (nodes []v1.Node) { } func generateEndpointSlices(endpointCount, portCount, updatablePortCount int, -) (endpointSlices []discovery.EndpointSlice) { +) (endpointSlices []*discovery.EndpointSlice) { servicePorts := generateUpdatablePorts(portCount, updatablePortCount) ports := make([]discovery.EndpointPort, 0, len(servicePorts)) @@ -1365,7 +1372,7 @@ func generateEndpointSlices(endpointCount, portCount, updatablePortCount int, }) } - endpointSlices = append(endpointSlices, discovery.EndpointSlice{ + endpointSlices = append(endpointSlices, &discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "endpointSlice", Labels: map[string]string{"kubernetes.io/service-name": "default-service"}, @@ -1415,13 +1422,47 @@ func generateUpdatablePorts(portCount int, updatableCount int) []v1.ServicePort return ports } -func NewFakeClient(endpointSlices []discovery.EndpointSlice, nodes []v1.Node) *fake.Clientset { - return fake.NewSimpleClientset( - &discovery.EndpointSliceList{ - Items: endpointSlices, - }, - &v1.NodeList{ - Items: nodes, - }, - ) +func NewFakeEndpointSliceLister(list []*discovery.EndpointSlice, err error) discoverylisters.EndpointSliceLister { + return &endpointSliceLister{ + list: list, + err: err, + } +} + +func NewFakeNodeLister(list []*v1.Node, err error) corelisters.NodeLister { + return &nodeLister{ + list: list, + err: err, + } +} + +type nodeLister struct { + list []*v1.Node + err error +} + +func (l *nodeLister) List(selector labels.Selector) (ret []*v1.Node, err error) { + return l.list, l.err +} + +// currently unused +func (l *nodeLister) Get(name string) (*v1.Node, error) { + return nil, nil +} + +type endpointSliceLister struct { + list []*discovery.EndpointSlice + err error +} + +func (l *endpointSliceLister) List(selector labels.Selector) (ret []*discovery.EndpointSlice, err error) { + return l.list, l.err +} + +func (l *endpointSliceLister) Get(name string) (*discovery.EndpointSlice, error) { + return nil, nil +} + +func (l *endpointSliceLister) EndpointSlices(name string) discoverylisters.EndpointSliceNamespaceLister { + return l } From 9775550cdaf0fb1113dfdfb0a5da9f25ac7b5d2a Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 19 Nov 2024 15:04:45 -0700 Subject: [PATCH 090/136] NLB-5872 If user removes nginxaas service annotation remove the service from the watcher's registe nodePort: 31575r --- internal/observation/watcher.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 21d6e02..83fc37a 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -252,14 +252,20 @@ func (w *Watcher) buildServiceEventHandlerForUpdate() func(interface{}, interfac slog.Info("Watcher::buildServiceEventHandlerForUpdate") return func(previous, updated interface{}) { // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API + previousService := previous.(*v1.Service) service := updated.(*v1.Service) + + if w.isDesiredService(previousService) && !w.isDesiredService(service) { + w.register.removeService(previousService) + return + } + if !w.isDesiredService(service) { return } w.register.addOrUpdateService(service) - previousService := previous.(*v1.Service) e := core.NewEvent(core.Updated, service, previousService) w.handler.AddRateLimitedEvent(&e) } From b962c8108a4024e41b991acb4fa8fac60c74ba4b Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 15 Nov 2024 10:04:26 -0700 Subject: [PATCH 091/136] NLB-5872 Bumped version to 0.8.0 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 39e898a..a3df0a6 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.7.1 +0.8.0 From da73e2700c969977f6c17f2367b3eddc6d2365a4 Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 8 Oct 2024 15:42:04 -0700 Subject: [PATCH 092/136] Cleanup Whitesource scan We need to set the product prefix needs to be set so we can view Whitesource scans properly for a given Whitesource project. Also, cleaned up some vars that are not needed here as we have defaults in the generic template. --- .gitlab-ci.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8237868..21fa6b6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -147,14 +147,8 @@ whitesource-scan: # - .go-cache - .whitesource-template-go variables: - NESTED: "true" - WS_WSS_URL: "https://f5.whitesourcesoftware.com/agent" - WS_APIKEY: "${WS_APIKEY_NGINX}" - WS_PRODUCTNAME: "N4A" - WS_PROJECTNAME: "${CI_PROJECT_NAME}" - WS_GO_MODULES_RESOLVEDEPENDENCIES: "true" - WS_GO_RESOLVEDEPENDENCIES: "false" - WS_GENERATEPROJECTDETAILSJSON: "true" + PRODUCT_PREFIX: "n4a" + WS_PROJECT: "${CI_PROJECT_NAME}" script: - *golang-private - !reference [.whitesource-template-go, script] From 1e88bdee8811a3a85fa9304a30af42068a9b07a8 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 8 Aug 2024 10:22:53 -0700 Subject: [PATCH 093/136] NLB-5384: Redeploy operator to run tests --- .gitlab-ci.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 21fa6b6..375a262 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,6 +17,7 @@ include: stages: - lint+test+build - security scanning + - e2e-tests - release - release-cnab @@ -218,6 +219,22 @@ container_scanning: when: never - when: on_success +trigger-e2e: + stage: e2e-tests + variables: + IS_TEST_ONLY: "true" + TEST_TYPE: "e2e.arm" + TEST_NLK_CHART_URL: "oci://${DEVOPS_DOCKER_URL_DEFAULT}/nginx-azure-lb/${CI_PROJECT_NAME}/charts/${CI_COMMIT_REF_SLUG}/nginxaas-loadbalancer-kubernetes" + TEST_NLK_IMG_TAG: ${CI_COMMIT_SHORT_SHA} + TEST_ARGS: "test_nlk.py" + AZ_LOCATION: "West Central US" + trigger: + project: f5/nginx/nginxazurelb/tools/nlbtest + branch: main + strategy: depend + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + .release-common: stage: release image: $DEVTOOLS_IMG From 5f0982f53ff7904c0366bbbd09be448b7315f69e Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 6 Dec 2024 16:01:27 -0800 Subject: [PATCH 094/136] Update chart,app version to 0.8.0 --- charts/nlk/Chart.yaml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index 4e1860f..b11ab86 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -1,17 +1,16 @@ --- apiVersion: v2 -appVersion: 0.4.0 +appVersion: 0.8.0 description: NGINXaaS LoadBalancer for Kubernetes name: nginxaas-loadbalancer-kubernetes keywords: -- nginx -- nginxaas -- loadbalancer + - nginx + - nginxaas + - loadbalancer kubeVersion: '>= 1.22.0-0' maintainers: -- name: "@ciroque" -- name: "@chrisakker" -- name: "@abdennour" - + - name: "@ciroque" + - name: "@chrisakker" + - name: "@abdennour" type: application -version: 0.4.0 +version: 0.8.0 From ef8ac000f5a94e1553005579c4e77e1d82b59f68 Mon Sep 17 00:00:00 2001 From: sarna Date: Fri, 6 Dec 2024 16:08:36 -0800 Subject: [PATCH 095/136] Fix chart,app version for AKS marketplace While nothing breaks if the versions are not aligned inside the AKS marketplace application bundle, the side effect is that when a user review the helm releases on an AKS cluster, they see the hardcoded app and chart version which is misleading. This commit fixes the chart and app version inside the Helm chart so that the user sees correct information while interacting with their AKS cluster. --- scripts/cnab.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/cnab.sh b/scripts/cnab.sh index 7d3c0b0..da6bdb4 100755 --- a/scripts/cnab.sh +++ b/scripts/cnab.sh @@ -25,6 +25,7 @@ set_version() { update_helm_chart() { yq -ie '.global.azure.images.nlk.registry = .nlk.image.registry | .global.azure.images.nlk.image = .nlk.image.repository | .global.azure.images.nlk.tag = env(VERSION)' charts/nlk/values.yaml + yq -ie '.version = env(VERSION) | .appVersion = env(VERSION)' charts/nlk/Chart.yaml } update_bundle() { From 9d5c093f90faf694f505ef1731952111ab6c3bc5 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 9 Dec 2024 14:47:51 -0800 Subject: [PATCH 096/136] Update default logger to be info The default log-level of "warn" does not help much even when there are issues with nlk: - While talking to the API. - updating upstreams. This commit updates the default loglevel to be info so that the customer sees useful logs while debugging the component. The user can choose to turn it down if they want to but having a slightly louder log level helps with the initial experience especially when the product is new (ish). --- charts/nlk/values.yaml | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index 851f67d..1f2f1f2 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -31,7 +31,7 @@ nlk: annotations: {} config: ## trace,debug,info,warn,error,fatal,panic - # logLevel: "warn" + logLevel: "info" ## the nginx hosts (comma-separated) to send upstream updates to nginxHosts: "" diff --git a/version b/version index a3df0a6..6f4eebd 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.8.0 +0.8.1 From 285af21583cc7bebf865d38654eaa1798d200e87 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 11 Dec 2024 08:48:04 -0700 Subject: [PATCH 097/136] NLB-5933 Remove handler; responsibilities for looking up upstream servers moved to synchronizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The synchronizer now assumes the responsibility of calling the translator to find the server addresses that need to be updated and handling errors by re-adding the work item to the queue. It continues to handle errors from the border client by requeuing workitems. The synchronizer needs to use a service identifier as the key in the workqueue (service ID=service name+namespace). If a workitem is dequeued, the synchronizer looks up the service in the shared informer cache to gain the most up-to-date state. The only time we use our local cache to discover the last known state of the service is in the case of a service deletion. The shared informer cache will no longer contain data on the deleted service, so we need to rely on our own local event cache to find details of the service's ports to determine the upstream and context, etc. Any incoming events overwrite stale events in the local event cache. Because the synchronizer always refers to the shared informer’s cache to find the current service addresses when the work item is dequeued we reduce the risk of applying stale state. This change simplifies event handling: there is only one rate-limited workqueue to be configured and managed, and we don't have to worry about potentially stale state being dequeued from two separate queues. --- cmd/nginx-loadbalancer-kubernetes/main.go | 17 +- internal/observation/handler.go | 141 -------------- internal/observation/handler_test.go | 58 ------ internal/observation/watcher.go | 28 +-- internal/observation/watcher_test.go | 4 +- internal/synchronization/cache.go | 40 ++++ internal/synchronization/synchronizer.go | 179 +++++++++++------- internal/synchronization/synchronizer_test.go | 156 ++++++++++++--- test/mocks/mock_handler.go | 26 --- 9 files changed, 304 insertions(+), 345 deletions(-) delete mode 100644 internal/observation/handler.go delete mode 100644 internal/observation/handler_test.go create mode 100644 internal/synchronization/cache.go delete mode 100644 test/mocks/mock_handler.go diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index b7381f0..501d7c4 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -49,11 +49,6 @@ func run() error { synchronizerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) - synchronizer, err := synchronization.NewSynchronizer(settings, synchronizerWorkqueue) - if err != nil { - return fmt.Errorf(`error initializing synchronizer: %w`, err) - } - factory := informers.NewSharedInformerFactoryWithOptions( k8sClient, settings.Watcher.ResyncPeriod, ) @@ -64,12 +59,15 @@ func run() error { nodesInformer := factory.Core().V1().Nodes() nodesLister := nodesInformer.Lister() - handlerWorkqueue := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) - translator := translation.NewTranslator(endpointSliceLister, nodesLister) - handler := observation.NewHandler(settings, synchronizer, handlerWorkqueue, translator) - watcher, err := observation.NewWatcher(settings, handler, serviceInformer, endpointSliceInformer, nodesInformer) + synchronizer, err := synchronization.NewSynchronizer( + settings, synchronizerWorkqueue, translator, serviceInformer.Lister()) + if err != nil { + return fmt.Errorf(`error initializing synchronizer: %w`, err) + } + + watcher, err := observation.NewWatcher(settings, synchronizer, serviceInformer, endpointSliceInformer, nodesInformer) if err != nil { return fmt.Errorf(`error occurred creating a watcher: %w`, err) } @@ -82,7 +80,6 @@ func run() error { } } - go handler.Run(ctx) go synchronizer.Run(ctx.Done()) probeServer := probation.NewHealthServer() diff --git a/internal/observation/handler.go b/internal/observation/handler.go deleted file mode 100644 index 45ae6c4..0000000 --- a/internal/observation/handler.go +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package observation - -import ( - "context" - "fmt" - "log/slog" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/util/workqueue" -) - -// HandlerInterface is the interface for the event handler -type HandlerInterface interface { - // AddRateLimitedEvent defines the interface for adding an event to the event queue - AddRateLimitedEvent(event *core.Event) - - // Run defines the interface used to start the event handler - Run(ctx context.Context) - - // ShutDown defines the interface used to stop the event handler - ShutDown() -} - -// Handler is responsible for processing events in the "nlk-handler" queue. -// When processing a message the Translation module is used to translate the event into an internal representation. -// The translation process may result in multiple events being generated. This fan-out mainly supports the differences -// in NGINX Plus API calls for creating/updating Upstreams and deleting Upstreams. -type Handler struct { - // eventQueue is the queue used to store events - eventQueue workqueue.RateLimitingInterface - - // settings is the configuration settings - settings configuration.Settings - - // synchronizer is the synchronizer used to synchronize the internal representation with a Border Server - synchronizer synchronization.Interface - - translator Translator -} - -type Translator interface { - Translate(*core.Event) (core.ServerUpdateEvents, error) -} - -// NewHandler creates a new event handler -func NewHandler( - settings configuration.Settings, - synchronizer synchronization.Interface, - eventQueue workqueue.RateLimitingInterface, - translator Translator, -) *Handler { - return &Handler{ - eventQueue: eventQueue, - settings: settings, - synchronizer: synchronizer, - translator: translator, - } -} - -// AddRateLimitedEvent adds an event to the event queue -func (h *Handler) AddRateLimitedEvent(event *core.Event) { - slog.Debug(`Handler::AddRateLimitedEvent`, "event", event) - h.eventQueue.AddRateLimited(event) -} - -// Run starts the event handler, spins up Goroutines to process events, and waits for context to be done -func (h *Handler) Run(ctx context.Context) { - slog.Debug("Handler::Run") - - worker := func() { - for h.handleNextEvent() { - // TODO: Add Telemetry - } - } - - for i := 0; i < h.settings.Handler.Threads; i++ { - go wait.Until(worker, 0, ctx.Done()) - } - - <-ctx.Done() -} - -// ShutDown stops the event handler and shuts down the event queue -func (h *Handler) ShutDown() { - slog.Debug("Handler::ShutDown") - h.eventQueue.ShutDown() -} - -// handleEvent feeds translated events to the synchronizer -func (h *Handler) handleEvent(e *core.Event) error { - slog.Debug("Handler::handleEvent", "event", e) - // TODO: Add Telemetry - - events, err := h.translator.Translate(e) - if err != nil { - return fmt.Errorf(`Handler::handleEvent error translating: %v`, err) - } - - h.synchronizer.AddEvents(events) - - return nil -} - -// handleNextEvent pulls an event from the event queue and feeds it to the event handler with retry logic -func (h *Handler) handleNextEvent() bool { - evt, quit := h.eventQueue.Get() - slog.Debug("Handler::handleNextEvent", "event", evt, "quit", quit) - if quit { - return false - } - - defer h.eventQueue.Done(evt) - - event := evt.(*core.Event) - h.withRetry(h.handleEvent(event), event) - - return true -} - -// withRetry handles errors from the event handler and requeues events that fail -func (h *Handler) withRetry(err error, event *core.Event) { - slog.Debug("Handler::withRetry") - if err != nil { - // TODO: Add Telemetry - if h.eventQueue.NumRequeues(event) < h.settings.Handler.RetryCount { - h.eventQueue.AddRateLimited(event) - slog.Info("Handler::withRetry: requeued event", "event", event, "error", err) - } else { - h.eventQueue.Forget(event) - slog.Warn(`Handler::withRetry: event has been dropped due to too many retries`, "event", event) - } - } // TODO: Add error logging -} diff --git a/internal/observation/handler_test.go b/internal/observation/handler_test.go deleted file mode 100644 index 550b318..0000000 --- a/internal/observation/handler_test.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package observation - -import ( - "testing" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" - v1 "k8s.io/api/core/v1" -) - -func TestHandler_AddsEventToSynchronizer(t *testing.T) { - t.Parallel() - synchronizer, handler := buildHandler() - - event := &core.Event{ - Type: core.Created, - Service: &v1.Service{ - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: "http-back", - }, - }, - }, - }, - } - - handler.AddRateLimitedEvent(event) - - handler.handleNextEvent() - - if len(synchronizer.Events) != 1 { - t.Errorf(`handler.AddRateLimitedEvent did not add the event to the queue`) - } -} - -func buildHandler() ( - *mocks.MockSynchronizer, *Handler, -) { - eventQueue := &mocks.MockRateLimiter{} - synchronizer := &mocks.MockSynchronizer{} - - handler := NewHandler(configuration.Settings{}, synchronizer, eventQueue, &fakeTranslator{}) - - return synchronizer, handler -} - -type fakeTranslator struct{} - -func (t *fakeTranslator) Translate(event *core.Event) (core.ServerUpdateEvents, error) { - return core.ServerUpdateEvents{{}}, nil -} diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 83fc37a..6c8977e 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -12,6 +12,7 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -24,8 +25,7 @@ import ( // Particularly, Services in the namespace defined in the WatcherSettings::NginxIngressNamespace setting. // When a change is detected, an Event is generated and added to the Handler's queue. type Watcher struct { - // handler is the event handler - handler HandlerInterface + synchronizer synchronization.Interface // settings is the configuration settings settings configuration.Settings @@ -45,7 +45,7 @@ type Watcher struct { // NewWatcher creates a new Watcher func NewWatcher( settings configuration.Settings, - handler HandlerInterface, + synchronizer synchronization.Interface, serviceInformer coreinformers.ServiceInformer, endpointSliceInformer discoveryinformers.EndpointSliceInformer, nodeInformer coreinformers.NodeInformer, @@ -67,7 +67,7 @@ func NewWatcher( nodesInformer := nodeInformer.Informer() w := &Watcher{ - handler: handler, + synchronizer: synchronizer, settings: settings, servicesInformer: servicesInformer, endpointSliceInformer: endpointSlicesInformer, @@ -92,7 +92,7 @@ func (w *Watcher) Run(ctx context.Context) error { slog.Debug("Watcher::Watch") defer utilruntime.HandleCrash() - defer w.handler.ShutDown() + defer w.synchronizer.ShutDown() <-ctx.Done() return nil @@ -115,7 +115,7 @@ func (w *Watcher) buildNodesEventHandlerForAdd() func(interface{}) { for _, service := range w.register.listServices() { var previousService *v1.Service e := core.NewEvent(core.Updated, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } } @@ -127,7 +127,7 @@ func (w *Watcher) buildNodesEventHandlerForUpdate() func(interface{}, interface{ for _, service := range w.register.listServices() { var previousService *v1.Service e := core.NewEvent(core.Updated, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } } @@ -139,7 +139,7 @@ func (w *Watcher) buildNodesEventHandlerForDelete() func(interface{}) { for _, service := range w.register.listServices() { var previousService *v1.Service e := core.NewEvent(core.Updated, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } } @@ -162,7 +162,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForAdd() func(interface{}) { var previousService *v1.Service e := core.NewEvent(core.Updated, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } @@ -184,7 +184,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForUpdate() func(interface{}, i var previousService *v1.Service e := core.NewEvent(core.Updated, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } @@ -206,7 +206,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForDelete() func(interface{}) { var previousService *v1.Service e := core.NewEvent(core.Deleted, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } @@ -224,7 +224,7 @@ func (w *Watcher) buildServiceEventHandlerForAdd() func(interface{}) { var previousService *v1.Service e := core.NewEvent(core.Created, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } @@ -242,7 +242,7 @@ func (w *Watcher) buildServiceEventHandlerForDelete() func(interface{}) { var previousService *v1.Service e := core.NewEvent(core.Deleted, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } @@ -267,7 +267,7 @@ func (w *Watcher) buildServiceEventHandlerForUpdate() func(interface{}, interfac w.register.addOrUpdateService(service) e := core.NewEvent(core.Updated, service, previousService) - w.handler.AddRateLimitedEvent(&e) + w.synchronizer.AddEvent(e) } } diff --git a/internal/observation/watcher_test.go b/internal/observation/watcher_test.go index 4618408..b8e8369 100644 --- a/internal/observation/watcher_test.go +++ b/internal/observation/watcher_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" "github.com/stretchr/testify/require" ) @@ -20,6 +19,5 @@ func TestWatcher_ErrWithNilInformers(t *testing.T) { } func buildWatcherWithNilInformer() (*Watcher, error) { - handler := &mocks.MockHandler{} - return NewWatcher(configuration.Settings{}, handler, nil, nil, nil) + return NewWatcher(configuration.Settings{}, nil, nil, nil, nil) } diff --git a/internal/synchronization/cache.go b/internal/synchronization/cache.go new file mode 100644 index 0000000..6c3f164 --- /dev/null +++ b/internal/synchronization/cache.go @@ -0,0 +1,40 @@ +package synchronization + +import ( + "sync" + + v1 "k8s.io/api/core/v1" +) + +// cache contains the most recent definitions for services monitored by NLK. +// We need these so that if a service is deleted from the shared informer cache, the +// caller can access the spec of the deleted service for cleanup. +type cache struct { + mu sync.RWMutex + store map[ServiceKey]*v1.Service +} + +func newCache() *cache { + return &cache{ + store: make(map[ServiceKey]*v1.Service), + } +} + +func (s *cache) get(key ServiceKey) (*v1.Service, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + service, ok := s.store[key] + return service, ok +} + +func (s *cache) add(key ServiceKey, service *v1.Service) { + s.mu.Lock() + defer s.mu.Unlock() + s.store[key] = service +} + +func (s *cache) delete(key ServiceKey) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.store, key) +} diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 20bc99a..fef9724 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -6,6 +6,7 @@ package synchronization import ( + "errors" "fmt" "log/slog" @@ -14,17 +15,16 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" nginxClient "github.com/nginxinc/nginx-plus-go-client/client" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" + corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/util/workqueue" ) // Interface defines the interface needed to implement a synchronizer. type Interface interface { - // AddEvents adds a list of events to the queue. - AddEvents(events core.ServerUpdateEvents) - // AddEvent adds an event to the queue. - AddEvent(event *core.ServerUpdateEvent) + AddEvent(event core.Event) // Run starts the synchronizer. Run(stopCh <-chan struct{}) @@ -33,60 +33,57 @@ type Interface interface { ShutDown() } +type Translator interface { + Translate(*core.Event) (core.ServerUpdateEvents, error) +} + +type ServiceKey struct { + Name string + Namespace string +} + // Synchronizer is responsible for synchronizing the state of the Border Servers. // Operating against the "nlk-synchronizer", it handles events by creating // a Border Client as specified in the Service annotation for the Upstream. // See application/border_client.go and application/application_constants.go for details. type Synchronizer struct { - eventQueue workqueue.RateLimitingInterface - settings configuration.Settings + eventQueue workqueue.RateLimitingInterface + settings configuration.Settings + translator Translator + cache *cache + serviceLister corelisters.ServiceLister } // NewSynchronizer creates a new Synchronizer. func NewSynchronizer( settings configuration.Settings, eventQueue workqueue.RateLimitingInterface, + translator Translator, + serviceLister corelisters.ServiceLister, ) (*Synchronizer, error) { synchronizer := Synchronizer{ - eventQueue: eventQueue, - settings: settings, + eventQueue: eventQueue, + settings: settings, + cache: newCache(), + translator: translator, + serviceLister: serviceLister, } return &synchronizer, nil } -// AddEvents adds a list of events to the queue. If no hosts are specified this is a null operation. -// Events will fan out to the number of hosts specified before being added to the queue. -func (s *Synchronizer) AddEvents(events core.ServerUpdateEvents) { - slog.Debug(`Synchronizer::AddEvents adding events`, slog.Int("eventCount", len(events))) +// AddEvent adds an event to the rate-limited queue. If no hosts are specified this is a null operation. +func (s *Synchronizer) AddEvent(event core.Event) { + slog.Debug(`Synchronizer::AddEvent`) if len(s.settings.NginxPlusHosts) == 0 { slog.Warn(`No Nginx Plus hosts were specified. Skipping synchronization.`) return } - updatedEvents := s.fanOutEventToHosts(events) - - for _, event := range updatedEvents { - s.AddEvent(event) - } -} - -// AddEvent adds an event to the queue. If no hosts are specified this is a null operation. -// Events will be added to the queue after a random delay between MinMillisecondsJitter and MaxMillisecondsJitter. -func (s *Synchronizer) AddEvent(event *core.ServerUpdateEvent) { - slog.Debug(`Synchronizer::AddEvent`, "event", event) - - if event.NginxHost == `` { - slog.Warn(`Nginx host was not specified. Skipping synchronization.`) - return - } - - after := RandomMilliseconds( - s.settings.Synchronizer.MinMillisecondsJitter, - s.settings.Synchronizer.MaxMillisecondsJitter, - ) - s.eventQueue.AddAfter(event, after) + key := ServiceKey{Name: event.Service.Name, Namespace: event.Service.Namespace} + s.cache.add(key, event.Service) + s.eventQueue.AddRateLimited(key) } // Run starts the Synchronizer, spins up Goroutines to process events, and waits for a stop signal. @@ -129,7 +126,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica // fanOutEventToHosts takes a list of events and returns a list of events, one for each Border Server. func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.ServerUpdateEvents { - slog.Debug(`Synchronizer::fanOutEventToHosts`, "event", event) + slog.Debug(`Synchronizer::fanOutEventToHosts`) var events core.ServerUpdateEvents @@ -145,33 +142,77 @@ func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.Se return events } -// handleEvent dispatches an event to the proper handler function. -func (s *Synchronizer) handleEvent(event *core.ServerUpdateEvent) error { - slog.Debug(`Synchronizer::handleEvent`, slog.String("eventID", event.ID)) - - var err error +// handleServiceEvent gets the latest state for the service from the shared +// informer cache, translates the service event into server update events and +// dispatches these events to the proper handler function. +func (s *Synchronizer) handleServiceEvent(key ServiceKey) (err error) { + logger := slog.With("service", key) + logger.Debug(`Synchronizer::handleServiceEvent`) + + // if a service exists in the shared informer cache, we can assume that we need to update it + event := core.Event{Type: core.Updated} + + namespaceLister := s.serviceLister.Services(key.Namespace) + k8sService, err := namespaceLister.Get(key.Name) + switch { + // the service has been deleted. We need to rely on the local cache to + // gather the last known state of the service so we can delete its + // upstream servers + case err != nil && apierrors.IsNotFound(err): + service, ok := s.cache.get(key) + if !ok { + logger.Warn(`Synchronizer::handleServiceEvent: no information could be gained about service`) + return nil + } + // no matter what type the cached event has, the service no longer exists, so the type is Deleted + event.Type = core.Deleted + event.Service = service + case err != nil: + return err + default: + event.Service = k8sService + } - switch event.Type { - case core.Created: - fallthrough + events, err := s.translator.Translate(&event) + if err != nil { + return err + } - case core.Updated: - err = s.handleCreatedUpdatedEvent(event) + if len(events) == 0 { + slog.Warn("Synchronizer::handleServiceEvent: no events to process") + return nil + } - case core.Deleted: - err = s.handleDeletedEvent(event) + events = s.fanOutEventToHosts(events) + + for _, evt := range events { + switch event.Type { + case core.Created, core.Updated: + if handleErr := s.handleCreatedUpdatedEvent(evt); handleErr != nil { + err = errors.Join(err, handleErr) + } + case core.Deleted: + if handleErr := s.handleDeletedEvent(evt); handleErr != nil { + err = errors.Join(err, handleErr) + } + default: + slog.Warn(`Synchronizer::handleServiceEvent: unknown event type`, "type", event.Type) + } + } - default: - slog.Warn(`Synchronizer::handleEvent: unknown event type`, "type", event.Type) + if err != nil { + return err } - if err == nil { - slog.Info( - "Synchronizer::handleEvent: successfully handled the event", - "type", event.TypeName(), "upstreamName", event.UpstreamName, "eventID", event.ID) + if event.Type == core.Deleted { + s.cache.delete(ServiceKey{Name: event.Service.Name, Namespace: event.Service.Namespace}) } - return err + slog.Debug( + "Synchronizer::handleServiceEvent: successfully handled the service change", "service", key, + ) + + return nil } // handleCreatedUpdatedEvent handles events of type Created or Updated. @@ -210,19 +251,25 @@ func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEv return nil } -// handleNextEvent pulls an event from the event queue and feeds it to the event handler with retry logic -func (s *Synchronizer) handleNextEvent() bool { - slog.Debug(`Synchronizer::handleNextEvent`) +// handleNextServiceEvent pulls a service from the event queue and feeds it to +// the service event handler with retry logic +func (s *Synchronizer) handleNextServiceEvent() bool { + slog.Debug(`Synchronizer::handleNextServiceEvent`) - evt, quit := s.eventQueue.Get() + svc, quit := s.eventQueue.Get() if quit { return false } - defer s.eventQueue.Done(evt) + defer s.eventQueue.Done(svc) + + key, ok := svc.(ServiceKey) + if !ok { + slog.Warn(`Synchronizer::handleNextServiceEvent: invalid service type`, "service", svc) + return true + } - event := evt.(*core.ServerUpdateEvent) - s.withRetry(s.handleEvent(event), event) + s.withRetry(s.handleServiceEvent(key), key) return true } @@ -230,18 +277,18 @@ func (s *Synchronizer) handleNextEvent() bool { // worker is the main message loop func (s *Synchronizer) worker() { slog.Debug(`Synchronizer::worker`) - for s.handleNextEvent() { + for s.handleNextServiceEvent() { } } // withRetry handles errors from the event handler and requeues events that fail -func (s *Synchronizer) withRetry(err error, event *core.ServerUpdateEvent) { +func (s *Synchronizer) withRetry(err error, key ServiceKey) { slog.Debug("Synchronizer::withRetry") if err != nil { // TODO: Add Telemetry - s.eventQueue.AddRateLimited(event) - slog.Info(`Synchronizer::withRetry: requeued event`, "eventID", event.ID, "error", err) + s.eventQueue.AddRateLimited(key) + slog.Info(`Synchronizer::withRetry: requeued service update`, "service", key, "error", err) } else { - s.eventQueue.Forget(event) + s.eventQueue.Forget(key) } // TODO: Add error logging } diff --git a/internal/synchronization/synchronizer_test.go b/internal/synchronization/synchronizer_test.go index d1710b2..7592f5c 100644 --- a/internal/synchronization/synchronizer_test.go +++ b/internal/synchronization/synchronizer_test.go @@ -13,6 +13,10 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/nginxinc/kubernetes-nginx-ingress/test/mocks" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + corelisters "k8s.io/client-go/listers/core/v1" ) func TestSynchronizer_NewSynchronizer(t *testing.T) { @@ -20,7 +24,12 @@ func TestSynchronizer_NewSynchronizer(t *testing.T) { rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(configuration.Settings{}, rateLimiter) + synchronizer, err := NewSynchronizer( + configuration.Settings{}, + rateLimiter, + &fakeTranslator{}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -33,17 +42,15 @@ func TestSynchronizer_NewSynchronizer(t *testing.T) { func TestSynchronizer_AddEventNoHosts(t *testing.T) { t.Parallel() const expectedEventCount = 0 - event := &core.ServerUpdateEvent{ - ID: "", - NginxHost: "", - Type: 0, - UpstreamName: "", - UpstreamServers: nil, - } rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(defaultSettings(), rateLimiter) + synchronizer, err := NewSynchronizer( + defaultSettings(), + rateLimiter, + &fakeTranslator{}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -54,7 +61,7 @@ func TestSynchronizer_AddEventNoHosts(t *testing.T) { // NOTE: Ideally we have a custom logger that can be mocked to capture the log message // and assert a warning was logged that the NGINX Plus host was not specified. - synchronizer.AddEvent(event) + synchronizer.AddEvent(core.Event{}) actualEventCount := rateLimiter.Len() if actualEventCount != expectedEventCount { t.Fatalf(`expected %v events, got %v`, expectedEventCount, actualEventCount) @@ -64,11 +71,16 @@ func TestSynchronizer_AddEventNoHosts(t *testing.T) { func TestSynchronizer_AddEventOneHost(t *testing.T) { t.Parallel() const expectedEventCount = 1 - events := buildEvents(1) + events := buildServerUpdateEvents(1) rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(defaultSettings("https://localhost:8080"), rateLimiter) + synchronizer, err := NewSynchronizer( + defaultSettings("https://localhost:8080"), + rateLimiter, + &fakeTranslator{events, nil}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -77,7 +89,7 @@ func TestSynchronizer_AddEventOneHost(t *testing.T) { t.Fatal("should have an Synchronizer instance") } - synchronizer.AddEvent(events[0]) + synchronizer.AddEvent(buildServiceUpdateEvent(1)) actualEventCount := rateLimiter.Len() if actualEventCount != expectedEventCount { t.Fatalf(`expected %v events, got %v`, expectedEventCount, actualEventCount) @@ -87,7 +99,7 @@ func TestSynchronizer_AddEventOneHost(t *testing.T) { func TestSynchronizer_AddEventManyHosts(t *testing.T) { t.Parallel() const expectedEventCount = 1 - events := buildEvents(1) + events := buildServerUpdateEvents(1) hosts := []string{ "https://localhost:8080", "https://localhost:8081", @@ -96,7 +108,12 @@ func TestSynchronizer_AddEventManyHosts(t *testing.T) { rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(defaultSettings(hosts...), rateLimiter) + synchronizer, err := NewSynchronizer( + defaultSettings(hosts...), + rateLimiter, + &fakeTranslator{events, nil}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -105,7 +122,7 @@ func TestSynchronizer_AddEventManyHosts(t *testing.T) { t.Fatal("should have an Synchronizer instance") } - synchronizer.AddEvent(events[0]) + synchronizer.AddEvent(buildServiceUpdateEvent(1)) actualEventCount := rateLimiter.Len() if actualEventCount != expectedEventCount { t.Fatalf(`expected %v events, got %v`, expectedEventCount, actualEventCount) @@ -115,10 +132,15 @@ func TestSynchronizer_AddEventManyHosts(t *testing.T) { func TestSynchronizer_AddEventsNoHosts(t *testing.T) { t.Parallel() const expectedEventCount = 0 - events := buildEvents(4) + events := buildServerUpdateEvents(4) rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(defaultSettings(), rateLimiter) + synchronizer, err := NewSynchronizer( + defaultSettings(), + rateLimiter, + &fakeTranslator{events, nil}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -129,7 +151,10 @@ func TestSynchronizer_AddEventsNoHosts(t *testing.T) { // NOTE: Ideally we have a custom logger that can be mocked to capture the log message // and assert a warning was logged that the NGINX Plus host was not specified. - synchronizer.AddEvents(events) + for i := 0; i < 4; i++ { + synchronizer.AddEvent(buildServiceUpdateEvent(i)) + } + actualEventCount := rateLimiter.Len() if actualEventCount != expectedEventCount { t.Fatalf(`expected %v events, got %v`, expectedEventCount, actualEventCount) @@ -139,10 +164,15 @@ func TestSynchronizer_AddEventsNoHosts(t *testing.T) { func TestSynchronizer_AddEventsOneHost(t *testing.T) { t.Parallel() const expectedEventCount = 4 - events := buildEvents(4) + events := buildServerUpdateEvents(1) rateLimiter := &mocks.MockRateLimiter{} - synchronizer, err := NewSynchronizer(defaultSettings("https://localhost:8080"), rateLimiter) + synchronizer, err := NewSynchronizer( + defaultSettings("https://localhost:8080"), + rateLimiter, + &fakeTranslator{events, nil}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -151,7 +181,10 @@ func TestSynchronizer_AddEventsOneHost(t *testing.T) { t.Fatal("should have an Synchronizer instance") } - synchronizer.AddEvents(events) + for i := 0; i < 4; i++ { + synchronizer.AddEvent(buildServiceUpdateEvent(i)) + } + actualEventCount := rateLimiter.Len() if actualEventCount != expectedEventCount { t.Fatalf(`expected %v events, got %v`, expectedEventCount, actualEventCount) @@ -161,7 +194,7 @@ func TestSynchronizer_AddEventsOneHost(t *testing.T) { func TestSynchronizer_AddEventsManyHosts(t *testing.T) { t.Parallel() const eventCount = 4 - events := buildEvents(eventCount) + events := buildServerUpdateEvents(eventCount) rateLimiter := &mocks.MockRateLimiter{} hosts := []string{ @@ -170,9 +203,14 @@ func TestSynchronizer_AddEventsManyHosts(t *testing.T) { "https://localhost:8082", } - expectedEventCount := eventCount * len(hosts) + expectedEventCount := 4 - synchronizer, err := NewSynchronizer(defaultSettings(hosts...), rateLimiter) + synchronizer, err := NewSynchronizer( + defaultSettings(hosts...), + rateLimiter, + &fakeTranslator{events, nil}, + newFakeServicesLister(defaultService()), + ) if err != nil { t.Fatalf(`should have been no error, %v`, err) } @@ -181,14 +219,28 @@ func TestSynchronizer_AddEventsManyHosts(t *testing.T) { t.Fatal("should have an Synchronizer instance") } - synchronizer.AddEvents(events) + for i := 0; i < eventCount; i++ { + synchronizer.AddEvent(buildServiceUpdateEvent(i)) + } + actualEventCount := rateLimiter.Len() if actualEventCount != expectedEventCount { t.Fatalf(`expected %v events, got %v`, expectedEventCount, actualEventCount) } } -func buildEvents(count int) core.ServerUpdateEvents { +func buildServiceUpdateEvent(serviceID int) core.Event { + return core.Event{ + Service: &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("test-service%d", serviceID), + Namespace: "test-namespace", + }, + }, + } +} + +func buildServerUpdateEvents(count int) core.ServerUpdateEvents { events := make(core.ServerUpdateEvents, count) for i := 0; i < count; i++ { events[i] = &core.ServerUpdateEvent{ @@ -218,3 +270,53 @@ func defaultSettings(nginxHosts ...string) configuration.Settings { }, } } + +type fakeTranslator struct { + events core.ServerUpdateEvents + err error +} + +func (t *fakeTranslator) Translate(event *core.Event) (core.ServerUpdateEvents, error) { + return t.events, t.err +} + +func newFakeServicesLister(list ...*v1.Service) corelisters.ServiceLister { + return &servicesLister{ + list: list, + } +} + +type servicesLister struct { + list []*v1.Service + err error +} + +func (l *servicesLister) List(selector labels.Selector) (ret []*v1.Service, err error) { + return l.list, l.err +} + +func (l *servicesLister) Get(name string) (*v1.Service, error) { + for _, service := range l.list { + if service.Name == name { + return service, nil + } + } + + return nil, nil +} + +func (l *servicesLister) Services(name string) corelisters.ServiceNamespaceLister { + return l +} + +func defaultService() *v1.Service { + return &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-service", + Labels: map[string]string{"kubernetes.io/service-name": "default-service"}, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeNodePort, + }, + } +} diff --git a/test/mocks/mock_handler.go b/test/mocks/mock_handler.go deleted file mode 100644 index f144cea..0000000 --- a/test/mocks/mock_handler.go +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package mocks - -import ( - "context" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" -) - -type MockHandler struct{} - -func (h *MockHandler) AddRateLimitedEvent(_ *core.Event) { -} - -func (h *MockHandler) Initialize() { -} - -func (h *MockHandler) Run(ctx context.Context) { -} - -func (h *MockHandler) ShutDown() { -} From 71e9c1552b987caa85400382f7e11000472d81fc Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 11 Dec 2024 10:31:48 -0700 Subject: [PATCH 098/136] NLB-5933 Added error group to the main routine Even though this is not really modifying the application's behavior right now (the watcher and synchronizer were only returning when the context was cancelled) it is a nicer framework for handling the errors of modules launched in separate goroutines if we do choose to make any of the NLK modules return errors. --- cmd/nginx-loadbalancer-kubernetes/main.go | 14 +++++++------- go.mod | 1 + go.sum | 2 ++ internal/synchronization/synchronizer.go | 10 ++++++---- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 501d7c4..5aba57f 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -17,6 +17,7 @@ import ( "github.com/nginxinc/kubernetes-nginx-ingress/internal/synchronization" "github.com/nginxinc/kubernetes-nginx-ingress/internal/translation" "github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" + "golang.org/x/sync/errgroup" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -80,18 +81,17 @@ func run() error { } } - go synchronizer.Run(ctx.Done()) + g, ctx := errgroup.WithContext(ctx) + + g.Go(func() error { return synchronizer.Run(ctx) }) probeServer := probation.NewHealthServer() probeServer.Start() - err = watcher.Run(ctx) - if err != nil { - return fmt.Errorf(`error occurred running watcher: %w`, err) - } + g.Go(func() error { return watcher.Run(ctx) }) - <-ctx.Done() - return nil + err = g.Wait() + return err } func initializeLogger(logLevel string) { diff --git a/go.mod b/go.mod index 94a1319..cc4fe63 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/nginxinc/nginx-plus-go-client v1.2.2 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.6.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 diff --git a/go.sum b/go.sum index 1433805..bd58f37 100644 --- a/go.sum +++ b/go.sum @@ -180,6 +180,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index fef9724..0d70215 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -6,6 +6,7 @@ package synchronization import ( + "context" "errors" "fmt" "log/slog" @@ -27,7 +28,7 @@ type Interface interface { AddEvent(event core.Event) // Run starts the synchronizer. - Run(stopCh <-chan struct{}) + Run(ctx context.Context) error // ShutDown shuts down the synchronizer. ShutDown() @@ -87,14 +88,15 @@ func (s *Synchronizer) AddEvent(event core.Event) { } // Run starts the Synchronizer, spins up Goroutines to process events, and waits for a stop signal. -func (s *Synchronizer) Run(stopCh <-chan struct{}) { +func (s *Synchronizer) Run(ctx context.Context) error { slog.Debug(`Synchronizer::Run`) for i := 0; i < s.settings.Synchronizer.Threads; i++ { - go wait.Until(s.worker, 0, stopCh) + go wait.Until(s.worker, 0, ctx.Done()) } - <-stopCh + <-ctx.Done() + return nil } // ShutDown stops the Synchronizer and shuts down the event queue From 1de0c2c3079556df747270b7ea379f441b87eb6c Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 17 Dec 2024 16:24:18 -0700 Subject: [PATCH 099/136] NLB-5933 Remove PreviousService from core event This field was not being used anywhere, so it seems better to get rid of it and avoid the hassle of keeping it accurate. --- internal/core/event.go | 10 +++------- internal/core/event_test.go | 7 +------ internal/observation/watcher.go | 26 +++++++++---------------- internal/translation/translator_test.go | 4 +--- 4 files changed, 14 insertions(+), 33 deletions(-) diff --git a/internal/core/event.go b/internal/core/event.go index 16d5d94..c32511e 100644 --- a/internal/core/event.go +++ b/internal/core/event.go @@ -29,16 +29,12 @@ type Event struct { // Service represents the service object in its current state Service *v1.Service - - // PreviousService represents the service object in its previous state - PreviousService *v1.Service } // NewEvent factory method to create a new Event -func NewEvent(eventType EventType, service *v1.Service, previousService *v1.Service) Event { +func NewEvent(eventType EventType, service *v1.Service) Event { return Event{ - Type: eventType, - Service: service, - PreviousService: previousService, + Type: eventType, + Service: service, } } diff --git a/internal/core/event_test.go b/internal/core/event_test.go index f0184fb..09724cf 100644 --- a/internal/core/event_test.go +++ b/internal/core/event_test.go @@ -10,9 +10,8 @@ func TestNewEvent(t *testing.T) { t.Parallel() expectedType := Created expectedService := &v1.Service{} - expectedPreviousService := &v1.Service{} - event := NewEvent(expectedType, expectedService, expectedPreviousService) + event := NewEvent(expectedType, expectedService) if event.Type != expectedType { t.Errorf("expected Created, got %v", event.Type) @@ -21,8 +20,4 @@ func TestNewEvent(t *testing.T) { if event.Service != expectedService { t.Errorf("expected service, got %#v", event.Service) } - - if event.PreviousService != expectedPreviousService { - t.Errorf("expected previous service, got %#v", event.PreviousService) - } } diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 6c8977e..0df49fe 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -113,8 +113,7 @@ func (w *Watcher) buildNodesEventHandlerForAdd() func(interface{}) { return func(obj interface{}) { slog.Debug("received node add event") for _, service := range w.register.listServices() { - var previousService *v1.Service - e := core.NewEvent(core.Updated, service, previousService) + e := core.NewEvent(core.Updated, service) w.synchronizer.AddEvent(e) } } @@ -125,8 +124,7 @@ func (w *Watcher) buildNodesEventHandlerForUpdate() func(interface{}, interface{ return func(previous, updated interface{}) { slog.Debug("received node update event") for _, service := range w.register.listServices() { - var previousService *v1.Service - e := core.NewEvent(core.Updated, service, previousService) + e := core.NewEvent(core.Updated, service) w.synchronizer.AddEvent(e) } } @@ -137,8 +135,7 @@ func (w *Watcher) buildNodesEventHandlerForDelete() func(interface{}) { return func(obj interface{}) { slog.Debug("received node delete event") for _, service := range w.register.listServices() { - var previousService *v1.Service - e := core.NewEvent(core.Updated, service, previousService) + e := core.NewEvent(core.Updated, service) w.synchronizer.AddEvent(e) } } @@ -160,8 +157,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForAdd() func(interface{}) { return } - var previousService *v1.Service - e := core.NewEvent(core.Updated, service, previousService) + e := core.NewEvent(core.Updated, service) w.synchronizer.AddEvent(e) } } @@ -182,8 +178,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForUpdate() func(interface{}, i return } - var previousService *v1.Service - e := core.NewEvent(core.Updated, service, previousService) + e := core.NewEvent(core.Updated, service) w.synchronizer.AddEvent(e) } } @@ -204,8 +199,7 @@ func (w *Watcher) buildEndpointSlicesEventHandlerForDelete() func(interface{}) { return } - var previousService *v1.Service - e := core.NewEvent(core.Deleted, service, previousService) + e := core.NewEvent(core.Deleted, service) w.synchronizer.AddEvent(e) } } @@ -222,8 +216,7 @@ func (w *Watcher) buildServiceEventHandlerForAdd() func(interface{}) { w.register.addOrUpdateService(service) - var previousService *v1.Service - e := core.NewEvent(core.Created, service, previousService) + e := core.NewEvent(core.Created, service) w.synchronizer.AddEvent(e) } } @@ -240,8 +233,7 @@ func (w *Watcher) buildServiceEventHandlerForDelete() func(interface{}) { w.register.removeService(service) - var previousService *v1.Service - e := core.NewEvent(core.Deleted, service, previousService) + e := core.NewEvent(core.Deleted, service) w.synchronizer.AddEvent(e) } } @@ -266,7 +258,7 @@ func (w *Watcher) buildServiceEventHandlerForUpdate() func(interface{}, interfac w.register.addOrUpdateService(service) - e := core.NewEvent(core.Updated, service, previousService) + e := core.NewEvent(core.Updated, service) w.synchronizer.AddEvent(e) } } diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index c6d42f5..11fa22c 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -1324,9 +1324,7 @@ func buildUpdatedEvent(service *v1.Service) core.Event { } func buildEvent(eventType core.EventType, service *v1.Service) core.Event { - previousService := defaultService(service.Spec.Type) - - event := core.NewEvent(eventType, service, previousService) + event := core.NewEvent(eventType, service) event.Service.Name = "default-service" return event } From a504336086064a553fdc0bcfd78593b153005b2d Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 19 Dec 2024 10:32:29 -0700 Subject: [PATCH 100/136] NLB-5933 Handle 404 errors when deleting upstream servers If a user removes a upstream server from their nginx configuration and then deletes the service associated with that upstream from their AKS cluster, we should handle the resulting 404s on the upstream servers and forget about the workitem, rather than retrying the updates indefinitely. Checking the error for a substring is not ideal as a way of handling the error, but the go plus client gives us no option here. --- internal/synchronization/synchronizer.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 0d70215..63b032a 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" "github.com/nginxinc/kubernetes-nginx-ingress/internal/communication" @@ -246,11 +247,17 @@ func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEv return fmt.Errorf(`error occurred creating the border client: %w`, err) } - if err = borderClient.Delete(serverUpdateEvent); err != nil { + err = borderClient.Update(serverUpdateEvent) + + switch { + case err == nil: + return nil + // checking the string is not ideal, but the plus client gives us no option + case strings.Contains(err.Error(), "status=404"): + return nil + default: return fmt.Errorf(`error occurred deleting the %s upstream servers: %w`, serverUpdateEvent.ClientType, err) } - - return nil } // handleNextServiceEvent pulls a service from the event queue and feeds it to From dec267fb0d41ca96df1c003c575532d76a956566 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 19 Dec 2024 14:58:53 -0700 Subject: [PATCH 101/136] NLB-5933 Bumped version to 0.8.2 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 6f4eebd..100435b 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.8.1 +0.8.2 From 82e91f88fe3d258c091cf5a46731f31a69773127 Mon Sep 17 00:00:00 2001 From: sarna Date: Sat, 4 Jan 2025 18:22:29 +0530 Subject: [PATCH 102/136] Upgrade the indirect net dep The golang.org/x/net package has a high CVE. --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index cc4fe63..502888d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/nginxinc/nginx-plus-go-client v1.2.2 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.6.0 + golang.org/x/sync v0.10.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 @@ -55,11 +55,11 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index bd58f37..26b1b01 100644 --- a/go.sum +++ b/go.sum @@ -169,8 +169,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= @@ -180,8 +180,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -190,18 +190,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 3aa3a4cc2deb9287ecec914753971da58c6c2358 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 9 Jan 2025 13:27:56 -0700 Subject: [PATCH 103/136] Upgrade nginx-plus-go-client to v2.2.0 This update brings changes that cause the client to apply all upstream server updates and not to return on first error. The upgrade necessitates changing the border client interfaces to match the new method signatures of the plus go client. As a result, I've threaded the context through various methods which did not require it before. --- go.mod | 6 ++-- go.sum | 4 +-- internal/application/border_client.go | 5 +-- .../application/nginx_client_interface.go | 14 ++++++-- .../application/nginx_http_border_client.go | 11 +++--- .../nginx_http_border_client_test.go | 9 ++--- .../application/nginx_stream_border_client.go | 11 +++--- .../nginx_stream_border_client_test.go | 9 ++--- internal/application/null_border_client.go | 5 +-- .../application/null_border_client_test.go | 9 +++-- internal/synchronization/synchronizer.go | 36 +++++++++---------- test/mocks/mock_nginx_plus_client.go | 12 +++++-- 12 files changed, 77 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 502888d..512dc36 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,12 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.21.2 +go 1.22.6 -toolchain go1.21.4 +toolchain go1.23.4 require ( - github.com/nginxinc/nginx-plus-go-client v1.2.2 + github.com/nginx/nginx-plus-go-client/v2 v2.2.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.10.0 diff --git a/go.sum b/go.sum index 26b1b01..cae00ce 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nginxinc/nginx-plus-go-client v1.2.2 h1:sl7HqNDDZq2EVu0eQQVoZ6PKYGa4h2dB/Qr5Ib0YKGw= -github.com/nginxinc/nginx-plus-go-client v1.2.2/go.mod h1:n8OFLzrJulJ2fur28Cwa1Qp5DZNS2VicLV+Adt30LQ4= +github.com/nginx/nginx-plus-go-client/v2 v2.2.0 h1:qwhx4fF/pq+h72/nE+o+XSH5mZmDU/R8fwim6VcZ8cM= +github.com/nginx/nginx-plus-go-client/v2 v2.2.0/go.mod h1:U7G5pqucUS1V4Uecs1xCsJ9knSsfwqhwu8ZEjoCYnmk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= diff --git a/internal/application/border_client.go b/internal/application/border_client.go index e4eded8..8bca843 100644 --- a/internal/application/border_client.go +++ b/internal/application/border_client.go @@ -6,6 +6,7 @@ package application import ( + "context" "fmt" "log/slog" @@ -14,8 +15,8 @@ import ( // Interface defines the functions required to implement a Border Client. type Interface interface { - Update(*core.ServerUpdateEvent) error - Delete(*core.ServerUpdateEvent) error + Update(context.Context, *core.ServerUpdateEvent) error + Delete(context.Context, *core.ServerUpdateEvent) error } // BorderClient defines any state need by the Border Client. diff --git a/internal/application/nginx_client_interface.go b/internal/application/nginx_client_interface.go index 31a7b94..1a60c5e 100644 --- a/internal/application/nginx_client_interface.go +++ b/internal/application/nginx_client_interface.go @@ -5,25 +5,33 @@ package application -import nginxClient "github.com/nginxinc/nginx-plus-go-client/client" +import ( + "context" + + nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" +) + +var _ NginxClientInterface = (*nginxClient.NginxClient)(nil) // NginxClientInterface defines the functions used on the NGINX Plus client, // abstracting away the full details of that client. type NginxClientInterface interface { // DeleteStreamServer is used by the NginxStreamBorderClient. - DeleteStreamServer(upstream string, server string) error + DeleteStreamServer(ctx context.Context, upstream string, server string) error // UpdateStreamServers is used by the NginxStreamBorderClient. UpdateStreamServers( + ctx context.Context, upstream string, servers []nginxClient.StreamUpstreamServer, ) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) // DeleteHTTPServer is used by the NginxHTTPBorderClient. - DeleteHTTPServer(upstream string, server string) error + DeleteHTTPServer(ctx context.Context, upstream string, server string) error // UpdateHTTPServers is used by the NginxHTTPBorderClient. UpdateHTTPServers( + ctx context.Context, upstream string, servers []nginxClient.UpstreamServer, ) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) diff --git a/internal/application/nginx_http_border_client.go b/internal/application/nginx_http_border_client.go index e9e754a..4de147e 100644 --- a/internal/application/nginx_http_border_client.go +++ b/internal/application/nginx_http_border_client.go @@ -7,10 +7,11 @@ package application import ( + "context" "fmt" + nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - nginxClient "github.com/nginxinc/nginx-plus-go-client/client" ) // NginxHttpBorderClient implements the BorderClient interface for HTTP upstreams. @@ -32,9 +33,9 @@ func NewNginxHTTPBorderClient(client interface{}) (Interface, error) { } // Update manages the Upstream servers for the Upstream Name given in the ServerUpdateEvent. -func (hbc *NginxHTTPBorderClient) Update(event *core.ServerUpdateEvent) error { +func (hbc *NginxHTTPBorderClient) Update(ctx context.Context, event *core.ServerUpdateEvent) error { httpUpstreamServers := asNginxHTTPUpstreamServers(event.UpstreamServers) - _, _, _, err := hbc.nginxClient.UpdateHTTPServers(event.UpstreamName, httpUpstreamServers) + _, _, _, err := hbc.nginxClient.UpdateHTTPServers(ctx, event.UpstreamName, httpUpstreamServers) if err != nil { return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err) } @@ -43,8 +44,8 @@ func (hbc *NginxHTTPBorderClient) Update(event *core.ServerUpdateEvent) error { } // Delete deletes the Upstream server for the Upstream Name given in the ServerUpdateEvent. -func (hbc *NginxHTTPBorderClient) Delete(event *core.ServerUpdateEvent) error { - err := hbc.nginxClient.DeleteHTTPServer(event.UpstreamName, event.UpstreamServers[0].Host) +func (hbc *NginxHTTPBorderClient) Delete(ctx context.Context, event *core.ServerUpdateEvent) error { + err := hbc.nginxClient.DeleteHTTPServer(ctx, event.UpstreamName, event.UpstreamServers[0].Host) if err != nil { return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err) } diff --git a/internal/application/nginx_http_border_client_test.go b/internal/application/nginx_http_border_client_test.go index 039b4ec..7a97206 100644 --- a/internal/application/nginx_http_border_client_test.go +++ b/internal/application/nginx_http_border_client_test.go @@ -9,6 +9,7 @@ package application import ( + "context" "testing" ) @@ -20,7 +21,7 @@ func TestHttpBorderClient_Delete(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Delete(event) + err = borderClient.Delete(context.Background(), event) if err != nil { t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) } @@ -38,7 +39,7 @@ func TestHttpBorderClient_Update(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Update(event) + err = borderClient.Update(context.Background(), event) if err != nil { t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) } @@ -65,7 +66,7 @@ func TestHttpBorderClient_DeleteReturnsError(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Delete(event) + err = borderClient.Delete(context.Background(), event) if err == nil { t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) @@ -80,7 +81,7 @@ func TestHttpBorderClient_UpdateReturnsError(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Update(event) + err = borderClient.Update(context.Background(), event) if err == nil { t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) diff --git a/internal/application/nginx_stream_border_client.go b/internal/application/nginx_stream_border_client.go index 19982b4..238a22b 100644 --- a/internal/application/nginx_stream_border_client.go +++ b/internal/application/nginx_stream_border_client.go @@ -7,10 +7,11 @@ package application import ( + "context" "fmt" + nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - nginxClient "github.com/nginxinc/nginx-plus-go-client/client" ) // NginxStreamBorderClient implements the BorderClient interface for stream upstreams. @@ -32,9 +33,9 @@ func NewNginxStreamBorderClient(client interface{}) (Interface, error) { } // Update manages the Upstream servers for the Upstream Name given in the ServerUpdateEvent. -func (tbc *NginxStreamBorderClient) Update(event *core.ServerUpdateEvent) error { +func (tbc *NginxStreamBorderClient) Update(ctx context.Context, event *core.ServerUpdateEvent) error { streamUpstreamServers := asNginxStreamUpstreamServers(event.UpstreamServers) - _, _, _, err := tbc.nginxClient.UpdateStreamServers(event.UpstreamName, streamUpstreamServers) + _, _, _, err := tbc.nginxClient.UpdateStreamServers(ctx, event.UpstreamName, streamUpstreamServers) if err != nil { return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err) } @@ -43,8 +44,8 @@ func (tbc *NginxStreamBorderClient) Update(event *core.ServerUpdateEvent) error } // Delete deletes the Upstream server for the Upstream Name given in the ServerUpdateEvent. -func (tbc *NginxStreamBorderClient) Delete(event *core.ServerUpdateEvent) error { - err := tbc.nginxClient.DeleteStreamServer(event.UpstreamName, event.UpstreamServers[0].Host) +func (tbc *NginxStreamBorderClient) Delete(ctx context.Context, event *core.ServerUpdateEvent) error { + err := tbc.nginxClient.DeleteStreamServer(ctx, event.UpstreamName, event.UpstreamServers[0].Host) if err != nil { return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err) } diff --git a/internal/application/nginx_stream_border_client_test.go b/internal/application/nginx_stream_border_client_test.go index c86a776..cf4d302 100644 --- a/internal/application/nginx_stream_border_client_test.go +++ b/internal/application/nginx_stream_border_client_test.go @@ -7,6 +7,7 @@ package application import ( + "context" "testing" ) @@ -18,7 +19,7 @@ func TestTcpBorderClient_Delete(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Delete(event) + err = borderClient.Delete(context.Background(), event) if err != nil { t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) } @@ -36,7 +37,7 @@ func TestTcpBorderClient_Update(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Update(event) + err = borderClient.Update(context.Background(), event) if err != nil { t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err) } @@ -63,7 +64,7 @@ func TestTcpBorderClient_DeleteReturnsError(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Delete(event) + err = borderClient.Delete(context.Background(), event) if err == nil { t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) @@ -78,7 +79,7 @@ func TestTcpBorderClient_UpdateReturnsError(t *testing.T) { t.Fatalf(`error occurred creating a new border client: %v`, err) } - err = borderClient.Update(event) + err = borderClient.Update(context.Background(), event) if err == nil { t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`) diff --git a/internal/application/null_border_client.go b/internal/application/null_border_client.go index 295ca62..dc4467d 100644 --- a/internal/application/null_border_client.go +++ b/internal/application/null_border_client.go @@ -6,6 +6,7 @@ package application import ( + "context" "log/slog" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" @@ -22,13 +23,13 @@ func NewNullBorderClient() (Interface, error) { } // Update logs a Warning. It is, after all, a NullObject Pattern implementation. -func (nbc *NullBorderClient) Update(_ *core.ServerUpdateEvent) error { +func (nbc *NullBorderClient) Update(_ context.Context, _ *core.ServerUpdateEvent) error { slog.Warn("NullBorderClient.Update called") return nil } // Delete logs a Warning. It is, after all, a NullObject Pattern implementation. -func (nbc *NullBorderClient) Delete(_ *core.ServerUpdateEvent) error { +func (nbc *NullBorderClient) Delete(_ context.Context, _ *core.ServerUpdateEvent) error { slog.Warn("NullBorderClient.Delete called") return nil } diff --git a/internal/application/null_border_client_test.go b/internal/application/null_border_client_test.go index f973949..01d9fe2 100644 --- a/internal/application/null_border_client_test.go +++ b/internal/application/null_border_client_test.go @@ -5,12 +5,15 @@ package application -import "testing" +import ( + "context" + "testing" +) func TestNullBorderClient_Delete(t *testing.T) { t.Parallel() client := NullBorderClient{} - err := client.Delete(nil) + err := client.Delete(context.Background(), nil) if err != nil { t.Errorf(`expected no error deleting border client, got: %v`, err) } @@ -19,7 +22,7 @@ func TestNullBorderClient_Delete(t *testing.T) { func TestNullBorderClient_Update(t *testing.T) { t.Parallel() client := NullBorderClient{} - err := client.Update(nil) + err := client.Update(context.Background(), nil) if err != nil { t.Errorf(`expected no error updating border client, got: %v`, err) } diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 63b032a..d45277b 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -12,11 +12,11 @@ import ( "log/slog" "strings" + nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" "github.com/nginxinc/kubernetes-nginx-ingress/internal/communication" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" - nginxClient "github.com/nginxinc/nginx-plus-go-client/client" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" corelisters "k8s.io/client-go/listers/core/v1" @@ -92,8 +92,15 @@ func (s *Synchronizer) AddEvent(event core.Event) { func (s *Synchronizer) Run(ctx context.Context) error { slog.Debug(`Synchronizer::Run`) + // worker is the main message loop + worker := func() { + slog.Debug(`Synchronizer::worker`) + for s.handleNextServiceEvent(ctx) { + } + } + for i := 0; i < s.settings.Synchronizer.Threads; i++ { - go wait.Until(s.worker, 0, ctx.Done()) + go wait.Until(worker, 0, ctx.Done()) } <-ctx.Done() @@ -148,7 +155,7 @@ func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.Se // handleServiceEvent gets the latest state for the service from the shared // informer cache, translates the service event into server update events and // dispatches these events to the proper handler function. -func (s *Synchronizer) handleServiceEvent(key ServiceKey) (err error) { +func (s *Synchronizer) handleServiceEvent(ctx context.Context, key ServiceKey) (err error) { logger := slog.With("service", key) logger.Debug(`Synchronizer::handleServiceEvent`) @@ -191,11 +198,11 @@ func (s *Synchronizer) handleServiceEvent(key ServiceKey) (err error) { for _, evt := range events { switch event.Type { case core.Created, core.Updated: - if handleErr := s.handleCreatedUpdatedEvent(evt); handleErr != nil { + if handleErr := s.handleCreatedUpdatedEvent(ctx, evt); handleErr != nil { err = errors.Join(err, handleErr) } case core.Deleted: - if handleErr := s.handleDeletedEvent(evt); handleErr != nil { + if handleErr := s.handleDeletedEvent(ctx, evt); handleErr != nil { err = errors.Join(err, handleErr) } default: @@ -219,7 +226,7 @@ func (s *Synchronizer) handleServiceEvent(key ServiceKey) (err error) { } // handleCreatedUpdatedEvent handles events of type Created or Updated. -func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerUpdateEvent) error { +func (s *Synchronizer) handleCreatedUpdatedEvent(ctx context.Context, serverUpdateEvent *core.ServerUpdateEvent) error { slog.Debug(`Synchronizer::handleCreatedUpdatedEvent`, "eventID", serverUpdateEvent.ID) var err error @@ -229,7 +236,7 @@ func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerU return fmt.Errorf(`error occurred creating the border client: %w`, err) } - if err = borderClient.Update(serverUpdateEvent); err != nil { + if err = borderClient.Update(ctx, serverUpdateEvent); err != nil { return fmt.Errorf(`error occurred updating the %s upstream servers: %w`, serverUpdateEvent.ClientType, err) } @@ -237,7 +244,7 @@ func (s *Synchronizer) handleCreatedUpdatedEvent(serverUpdateEvent *core.ServerU } // handleDeletedEvent handles events of type Deleted. -func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEvent) error { +func (s *Synchronizer) handleDeletedEvent(ctx context.Context, serverUpdateEvent *core.ServerUpdateEvent) error { slog.Debug(`Synchronizer::handleDeletedEvent`, "eventID", serverUpdateEvent.ID) var err error @@ -247,7 +254,7 @@ func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEv return fmt.Errorf(`error occurred creating the border client: %w`, err) } - err = borderClient.Update(serverUpdateEvent) + err = borderClient.Update(ctx, serverUpdateEvent) switch { case err == nil: @@ -262,7 +269,7 @@ func (s *Synchronizer) handleDeletedEvent(serverUpdateEvent *core.ServerUpdateEv // handleNextServiceEvent pulls a service from the event queue and feeds it to // the service event handler with retry logic -func (s *Synchronizer) handleNextServiceEvent() bool { +func (s *Synchronizer) handleNextServiceEvent(ctx context.Context) bool { slog.Debug(`Synchronizer::handleNextServiceEvent`) svc, quit := s.eventQueue.Get() @@ -278,18 +285,11 @@ func (s *Synchronizer) handleNextServiceEvent() bool { return true } - s.withRetry(s.handleServiceEvent(key), key) + s.withRetry(s.handleServiceEvent(ctx, key), key) return true } -// worker is the main message loop -func (s *Synchronizer) worker() { - slog.Debug(`Synchronizer::worker`) - for s.handleNextServiceEvent() { - } -} - // withRetry handles errors from the event handler and requeues events that fail func (s *Synchronizer) withRetry(err error, key ServiceKey) { slog.Debug("Synchronizer::withRetry") diff --git a/test/mocks/mock_nginx_plus_client.go b/test/mocks/mock_nginx_plus_client.go index 00b560e..991ab08 100644 --- a/test/mocks/mock_nginx_plus_client.go +++ b/test/mocks/mock_nginx_plus_client.go @@ -5,7 +5,11 @@ package mocks -import nginxClient "github.com/nginxinc/nginx-plus-go-client/client" +import ( + "context" + + nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" +) type MockNginxClient struct { CalledFunctions map[string]bool @@ -26,7 +30,7 @@ func NewErroringMockClient(err error) *MockNginxClient { } } -func (m MockNginxClient) DeleteStreamServer(_ string, _ string) error { +func (m MockNginxClient) DeleteStreamServer(_ context.Context, _ string, _ string) error { m.CalledFunctions["DeleteStreamServer"] = true if m.Error != nil { @@ -37,6 +41,7 @@ func (m MockNginxClient) DeleteStreamServer(_ string, _ string) error { } func (m MockNginxClient) UpdateStreamServers( + _ context.Context, _ string, _ []nginxClient.StreamUpstreamServer, ) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error) { @@ -49,7 +54,7 @@ func (m MockNginxClient) UpdateStreamServers( return nil, nil, nil, nil } -func (m MockNginxClient) DeleteHTTPServer(_ string, _ string) error { +func (m MockNginxClient) DeleteHTTPServer(_ context.Context, _ string, _ string) error { m.CalledFunctions["DeleteHTTPServer"] = true if m.Error != nil { @@ -60,6 +65,7 @@ func (m MockNginxClient) DeleteHTTPServer(_ string, _ string) error { } func (m MockNginxClient) UpdateHTTPServers( + _ context.Context, _ string, _ []nginxClient.UpstreamServer, ) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error) { From 5fd552e07ee612ee5bd4ba0cc048bb1f1545b755 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 9 Jan 2025 15:36:19 -0700 Subject: [PATCH 104/136] Bumped version to 0.8.3 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 100435b..ee94dd8 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.8.2 +0.8.3 From 8827dd1595c2e1a42f64125eb554d531a34f05c6 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 21 Jan 2025 16:09:41 -0700 Subject: [PATCH 105/136] NLB-6070 Removal of service annotation is equivalent to service deletion We interpret the removal of the nginxaas service annotation as a sign from the user that they no longer wish us to route traffic to the upstreams formerly associated with theservice. It is as if they were at the starting point of not yet having configured NLK to monitor a specific kubernetes service. When we see that the nginxaas service annotation has been removed, we delete all upstream servers from the upstreams associated with the service. --- internal/observation/watcher.go | 4 +++- internal/synchronization/cache.go | 21 +++++++++++++++------ internal/synchronization/synchronizer.go | 18 ++++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/internal/observation/watcher.go b/internal/observation/watcher.go index 0df49fe..9711347 100644 --- a/internal/observation/watcher.go +++ b/internal/observation/watcher.go @@ -243,12 +243,14 @@ func (w *Watcher) buildServiceEventHandlerForDelete() func(interface{}) { func (w *Watcher) buildServiceEventHandlerForUpdate() func(interface{}, interface{}) { slog.Info("Watcher::buildServiceEventHandlerForUpdate") return func(previous, updated interface{}) { - // TODO NLB-5435 Check for user removing annotation and send delete request to dataplane API previousService := previous.(*v1.Service) service := updated.(*v1.Service) if w.isDesiredService(previousService) && !w.isDesiredService(service) { + slog.Info("Watcher::service annotation removed", "serviceName", service.Name) w.register.removeService(previousService) + e := core.NewEvent(core.Deleted, previousService) + w.synchronizer.AddEvent(e) return } diff --git a/internal/synchronization/cache.go b/internal/synchronization/cache.go index 6c3f164..14effb9 100644 --- a/internal/synchronization/cache.go +++ b/internal/synchronization/cache.go @@ -2,6 +2,7 @@ package synchronization import ( "sync" + "time" v1 "k8s.io/api/core/v1" ) @@ -11,23 +12,31 @@ import ( // caller can access the spec of the deleted service for cleanup. type cache struct { mu sync.RWMutex - store map[ServiceKey]*v1.Service + store map[ServiceKey]service +} + +type service struct { + service *v1.Service + // removedAt indicates when the service was removed from NGINXaaS + // monitoring. A zero time indicates that the service is still actively + // being monitored by NGINXaaS. + removedAt time.Time } func newCache() *cache { return &cache{ - store: make(map[ServiceKey]*v1.Service), + store: make(map[ServiceKey]service), } } -func (s *cache) get(key ServiceKey) (*v1.Service, bool) { +func (s *cache) get(key ServiceKey) (service, bool) { s.mu.RLock() defer s.mu.RUnlock() - service, ok := s.store[key] - return service, ok + svc, ok := s.store[key] + return svc, ok } -func (s *cache) add(key ServiceKey, service *v1.Service) { +func (s *cache) add(key ServiceKey, service service) { s.mu.Lock() defer s.mu.Unlock() s.store[key] = service diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index d45277b..82b3071 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -11,6 +11,7 @@ import ( "fmt" "log/slog" "strings" + "time" nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" "github.com/nginxinc/kubernetes-nginx-ingress/internal/application" @@ -84,7 +85,12 @@ func (s *Synchronizer) AddEvent(event core.Event) { } key := ServiceKey{Name: event.Service.Name, Namespace: event.Service.Namespace} - s.cache.add(key, event.Service) + var deletedAt time.Time + if event.Type == core.Deleted { + deletedAt = time.Now() + } + + s.cache.add(key, service{event.Service, deletedAt}) s.eventQueue.AddRateLimited(key) } @@ -162,6 +168,8 @@ func (s *Synchronizer) handleServiceEvent(ctx context.Context, key ServiceKey) ( // if a service exists in the shared informer cache, we can assume that we need to update it event := core.Event{Type: core.Updated} + cachedService, exists := s.cache.get(key) + namespaceLister := s.serviceLister.Services(key.Namespace) k8sService, err := namespaceLister.Get(key.Name) switch { @@ -169,16 +177,18 @@ func (s *Synchronizer) handleServiceEvent(ctx context.Context, key ServiceKey) ( // gather the last known state of the service so we can delete its // upstream servers case err != nil && apierrors.IsNotFound(err): - service, ok := s.cache.get(key) - if !ok { + if !exists { logger.Warn(`Synchronizer::handleServiceEvent: no information could be gained about service`) return nil } // no matter what type the cached event has, the service no longer exists, so the type is Deleted event.Type = core.Deleted - event.Service = service + event.Service = cachedService.service case err != nil: return err + case exists && !cachedService.removedAt.IsZero(): + event.Type = core.Deleted + event.Service = cachedService.service default: event.Service = k8sService } From e839b5c70a15511aafde4e036273e76310433bce Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 21 Jan 2025 16:27:06 -0700 Subject: [PATCH 106/136] NLB-6070 bumped version to 0.8.4 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index ee94dd8..b60d719 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.8.3 +0.8.4 From da4caf026bb97d76bd99ad4563f8c55d22f4e07f Mon Sep 17 00:00:00 2001 From: sarna Date: Wed, 29 Jan 2025 16:25:19 -0800 Subject: [PATCH 107/136] Use upstream SAST template We were using the deprecated internal SAST template which was retired a while back: https://gitlab.com/f5/nginx/tools/devops-utils/-/merge_requests/204. There was a change made to the upstream template that is included in the internal template which breaks the internal template as there were some jobs that were removed from the upstream. This commit moves us over to use the official upstream template. --- .gitlab-ci.yml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 375a262..04dffff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,9 +9,7 @@ include: - project: "f5/nginx/tools/devops-utils" file: "include/devops-whitesource.yaml" ref: "master" - - project: "f5/nginx/tools/devops-utils" - file: "/include/gitlab-sast.yml" - ref: "master" + - template: Jobs/SAST.gitlab-ci.yml - template: Jobs/Container-Scanning.gitlab-ci.yml stages: @@ -163,15 +161,6 @@ whitesource-scan: - ${CI_PROJECT_DIR}/whitesource/ expire_in: 1 weeks -################################################################################ -# GitLab SAST security scanning -# CISO requires that all products be scanned statically for vulnerabilities. -# For more information, please see: -# https://gitlab.com/f5/nginx/tools/devops-utils/-/blob/master/include/gitlab-sast.md -# NOTE: please do not alter, change, modify, or overwrite job rules, job -# variables, or runtime states in order to maintain compliance to CISO -# requirements. -################################################################################ sast: stage: lint+test+build From 751ff3f851fd19e2fad09e34fe111bf783f51b32 Mon Sep 17 00:00:00 2001 From: Dylan WAY Date: Wed, 29 Jan 2025 14:21:56 -0700 Subject: [PATCH 108/136] NLB-5828: update nginx-plus-go-client version Pulls in the latest nginx-plus-go-client to make use of some optimizations in the UpdateStreamServers and UpdateHTTPServers calls. --- go.mod | 2 +- go.sum | 4 ++-- version | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 512dc36..f7c3dc8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ go 1.22.6 toolchain go1.23.4 require ( - github.com/nginx/nginx-plus-go-client/v2 v2.2.0 + github.com/nginx/nginx-plus-go-client/v2 v2.3.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.10.0 diff --git a/go.sum b/go.sum index cae00ce..505b7f6 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nginx/nginx-plus-go-client/v2 v2.2.0 h1:qwhx4fF/pq+h72/nE+o+XSH5mZmDU/R8fwim6VcZ8cM= -github.com/nginx/nginx-plus-go-client/v2 v2.2.0/go.mod h1:U7G5pqucUS1V4Uecs1xCsJ9knSsfwqhwu8ZEjoCYnmk= +github.com/nginx/nginx-plus-go-client/v2 v2.3.0 h1:ciKh1lwadNzUaOGjLcKWu/BGigASxU6p7v/6US71fhA= +github.com/nginx/nginx-plus-go-client/v2 v2.3.0/go.mod h1:U7G5pqucUS1V4Uecs1xCsJ9knSsfwqhwu8ZEjoCYnmk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= diff --git a/version b/version index b60d719..7ada0d3 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.8.4 +0.8.5 From 1c47001fe1374fc6d97d0cfcb637a36580c002fe Mon Sep 17 00:00:00 2001 From: sarna Date: Tue, 4 Feb 2025 12:08:07 -0800 Subject: [PATCH 109/136] Release NLK 1.0.0 This marks the official GA release for NLK. The updated semver should help communicate the readiness of the integration and the product to the customers. --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 7ada0d3..3eefcb9 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.8.5 +1.0.0 From 5ce472a8cbc15f5cbac59459ea36f1ea8700f1a3 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Mon, 10 Feb 2025 14:43:17 -0700 Subject: [PATCH 110/136] NLB-6230 Handle services of type LoadBalancer NLK listens for changes to any kubernetes service of type LoadBalancer and updates the NGINXaaS upstreams with the external IPs and ports of the LB service. We are using the external IPs and not the node IPs so as to provide seamless plug-and-play functionality with the customer's networking framework. --- internal/translation/translator.go | 42 ++++- internal/translation/translator_test.go | 210 +++++++++++++++++++----- 2 files changed, 210 insertions(+), 42 deletions(-) diff --git a/internal/translation/translator.go b/internal/translation/translator.go index 117c0b9..8491a66 100644 --- a/internal/translation/translator.go +++ b/internal/translation/translator.go @@ -56,6 +56,8 @@ func (t *Translator) buildServerUpdateEvents(ports []v1.ServicePort, event *core return t.buildNodeIPEvents(ports, event) case v1.ServiceTypeClusterIP: return t.buildClusterIPEvents(event) + case v1.ServiceTypeLoadBalancer: + return t.buildLoadBalancerEvents(event) default: return events, fmt.Errorf("unsupported service type: %s", event.Service.Spec.Type) } @@ -66,6 +68,38 @@ type upstream struct { name string } +func (t *Translator) buildLoadBalancerEvents(event *core.Event) (events core.ServerUpdateEvents, err error) { + slog.Debug("Translator::buildLoadBalancerEvents", "ports", event.Service.Spec.Ports) + + addresses := make([]string, 0, len(event.Service.Status.LoadBalancer.Ingress)) + for _, ingress := range event.Service.Status.LoadBalancer.Ingress { + addresses = append(addresses, ingress.IP) + } + + for _, port := range event.Service.Spec.Ports { + context, upstreamName, err := getContextAndUpstreamName(port.Name) + if err != nil { + slog.Info("Translator::buildLoadBalancerEvents: ignoring port", "err", err, "name", port.Name) + continue + } + + upstreamServers := buildUpstreamServers(addresses, port.Port) + + switch event.Type { + case core.Created, core.Updated: + events = append(events, core.NewServerUpdateEvent(event.Type, upstreamName, context, upstreamServers)) + case core.Deleted: + events = append(events, core.NewServerUpdateEvent( + core.Updated, upstreamName, context, nil, + )) + default: + slog.Warn(`Translator::buildLoadBalancerEvents: unknown event type`, "type", event.Type) + } + } + + return events, nil +} + func (t *Translator) buildClusterIPEvents(event *core.Event) (events core.ServerUpdateEvents, err error) { namespace := event.Service.GetObjectMeta().GetNamespace() serviceName := event.Service.Name @@ -153,7 +187,7 @@ func (t *Translator) buildNodeIPEvents(ports []v1.ServicePort, event *core.Event return nil, err } - upstreamServers := buildUpstreamServers(addresses, port) + upstreamServers := buildUpstreamServers(addresses, port.NodePort) switch event.Type { case core.Created: @@ -175,11 +209,11 @@ func (t *Translator) buildNodeIPEvents(ports []v1.ServicePort, event *core.Event return events, nil } -func buildUpstreamServers(nodeIPs []string, port v1.ServicePort) core.UpstreamServers { +func buildUpstreamServers(ipAddresses []string, port int32) core.UpstreamServers { var servers core.UpstreamServers - for _, nodeIP := range nodeIPs { - host := fmt.Sprintf("%s:%d", nodeIP, port.NodePort) + for _, ip := range ipAddresses { + host := fmt.Sprintf("%s:%d", ip, port) server := core.NewUpstreamServer(host) servers = append(servers, server) } diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go index 11fa22c..309a07c 100644 --- a/internal/translation/translator_test.go +++ b/internal/translation/translator_test.go @@ -39,8 +39,9 @@ const ( func TestCreatedTranslateNoPorts(t *testing.T) { t.Parallel() testcases := map[string]struct{ serviceType v1.ServiceType }{ - "nodePort": {v1.ServiceTypeNodePort}, - "clusterIP": {v1.ServiceTypeClusterIP}, + "nodePort": {v1.ServiceTypeNodePort}, + "clusterIP": {v1.ServiceTypeClusterIP}, + "loadBalancer": {v1.ServiceTypeLoadBalancer}, } for name, tc := range testcases { @@ -51,7 +52,7 @@ func TestCreatedTranslateNoPorts(t *testing.T) { const expectedEventCount = 0 service := defaultService(tc.serviceType) - event := buildCreatedEvent(service) + event := buildCreatedEvent(service, 0) translator := NewTranslator( NewFakeEndpointSliceLister([]*discovery.EndpointSlice{}, nil), @@ -74,8 +75,9 @@ func TestCreatedTranslateNoPorts(t *testing.T) { func TestCreatedTranslateNoInterestingPorts(t *testing.T) { t.Parallel() testcases := map[string]struct{ serviceType v1.ServiceType }{ - "nodePort": {v1.ServiceTypeNodePort}, - "clusterIP": {v1.ServiceTypeClusterIP}, + "nodePort": {v1.ServiceTypeNodePort}, + "clusterIP": {v1.ServiceTypeClusterIP}, + "loadBalancer": {v1.ServiceTypeLoadBalancer}, } for name, tc := range testcases { @@ -88,7 +90,7 @@ func TestCreatedTranslateNoInterestingPorts(t *testing.T) { ports := generateUpdatablePorts(portCount, 0) service := serviceWithPorts(tc.serviceType, ports) - event := buildCreatedEvent(service) + event := buildCreatedEvent(service, 0) translator := NewTranslator( NewFakeEndpointSliceLister([]*discovery.EndpointSlice{}, nil), @@ -114,6 +116,7 @@ func TestCreatedTranslateOneInterestingPort(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -127,6 +130,11 @@ func TestCreatedTranslateOneInterestingPort(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 1, 1), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: 1, + }, } for name, tc := range testcases { @@ -139,7 +147,7 @@ func TestCreatedTranslateOneInterestingPort(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildCreatedEvent(service) + event := buildCreatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -163,6 +171,7 @@ func TestCreatedTranslateManyInterestingPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -176,6 +185,11 @@ func TestCreatedTranslateManyInterestingPorts(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 4, 4), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: 1, + }, } for name, tc := range testcases { @@ -188,7 +202,7 @@ func TestCreatedTranslateManyInterestingPorts(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildCreatedEvent(service) + event := buildCreatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -213,6 +227,7 @@ func TestCreatedTranslateManyMixedPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -226,6 +241,11 @@ func TestCreatedTranslateManyMixedPorts(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 6, 2), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: 1, + }, } for name, tc := range testcases { @@ -239,7 +259,7 @@ func TestCreatedTranslateManyMixedPorts(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildCreatedEvent(service) + event := buildCreatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -263,6 +283,7 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -276,6 +297,11 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { endpoints: generateEndpointSlices(ManyEndpointSlices, 6, 2), expectedServerCount: ManyEndpointSlices, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: ManyNodes, + expectedServerCount: ManyNodes, + }, } for name, tc := range testcases { @@ -288,7 +314,7 @@ func TestCreatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildCreatedEvent(service) + event := buildCreatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -316,6 +342,7 @@ func TestUpdatedTranslateNoPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -329,6 +356,11 @@ func TestUpdatedTranslateNoPorts(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 0, 0), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -338,7 +370,7 @@ func TestUpdatedTranslateNoPorts(t *testing.T) { const expectedEventCount = 0 service := defaultService(tc.serviceType) - event := buildUpdatedEvent(service) + event := buildUpdatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -360,6 +392,7 @@ func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -373,6 +406,11 @@ func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 1, 0), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -384,7 +422,7 @@ func TestUpdatedTranslateNoInterestingPorts(t *testing.T) { ports := generateUpdatablePorts(portCount, 0) service := serviceWithPorts(tc.serviceType, ports) - event := buildUpdatedEvent(service) + event := buildUpdatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -406,6 +444,7 @@ func TestUpdatedTranslateOneInterestingPort(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -419,6 +458,11 @@ func TestUpdatedTranslateOneInterestingPort(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 1, 1), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -430,7 +474,7 @@ func TestUpdatedTranslateOneInterestingPort(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildUpdatedEvent(service) + event := buildUpdatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -455,6 +499,7 @@ func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -468,6 +513,11 @@ func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 4, 4), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -479,7 +529,7 @@ func TestUpdatedTranslateManyInterestingPorts(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildUpdatedEvent(service) + event := buildUpdatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -504,6 +554,7 @@ func TestUpdatedTranslateManyMixedPorts(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -517,6 +568,11 @@ func TestUpdatedTranslateManyMixedPorts(t *testing.T) { endpoints: generateEndpointSlices(OneEndpointSlice, 6, 2), expectedServerCount: OneEndpointSlice, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: OneNode, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -529,7 +585,7 @@ func TestUpdatedTranslateManyMixedPorts(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildUpdatedEvent(service) + event := buildUpdatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -554,6 +610,7 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { testcases := map[string]struct { serviceType v1.ServiceType nodes []*v1.Node + ingresses int endpoints []*discovery.EndpointSlice expectedServerCount int }{ @@ -567,6 +624,11 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { endpoints: generateEndpointSlices(ManyEndpointSlices, 6, 2), expectedServerCount: ManyEndpointSlices, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + expectedServerCount: ManyNodes, + ingresses: ManyNodes, + }, } for name, tc := range testcases { @@ -579,7 +641,7 @@ func TestUpdatedTranslateManyMixedPortsAndManyNodes(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildUpdatedEvent(service) + event := buildUpdatedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -608,6 +670,7 @@ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -615,6 +678,9 @@ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { "clusterIP": { serviceType: v1.ServiceTypeClusterIP, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + }, } for name, tc := range testcases { @@ -625,7 +691,7 @@ func TestDeletedTranslateNoPortsAndNoNodes(t *testing.T) { const expectedEventCount = 0 service := defaultService(tc.serviceType) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -650,6 +716,7 @@ func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -657,6 +724,9 @@ func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { "clusterIP": { serviceType: v1.ServiceTypeClusterIP, }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + }, } for name, tc := range testcases { @@ -668,7 +738,7 @@ func TestDeletedTranslateNoInterestingPortsAndNoNodes(t *testing.T) { ports := generateUpdatablePorts(portCount, 0) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -694,6 +764,7 @@ func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -702,6 +773,9 @@ func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(0, 1, 1), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + }, } for name, tc := range testcases { @@ -714,7 +788,7 @@ func TestDeletedTranslateOneInterestingPortAndNoNodes(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -740,6 +814,7 @@ func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -748,6 +823,9 @@ func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(0, 4, 4), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + }, } for name, tc := range testcases { @@ -759,7 +837,7 @@ func TestDeletedTranslateManyInterestingPortsAndNoNodes(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -784,6 +862,7 @@ func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -792,6 +871,9 @@ func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(0, 6, 2), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + }, } for name, tc := range testcases { @@ -805,7 +887,7 @@ func TestDeletedTranslateManyMixedPortsAndNoNodes(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -831,6 +913,7 @@ func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -840,6 +923,10 @@ func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(OneEndpointSlice, 0, 0), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -850,7 +937,7 @@ func TestDeletedTranslateNoPortsAndOneNode(t *testing.T) { const expectedEventCount = 0 service := defaultService(tc.serviceType) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -875,6 +962,7 @@ func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -884,6 +972,10 @@ func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(OneEndpointSlice, 1, 0), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -895,7 +987,7 @@ func TestDeletedTranslateNoInterestingPortsAndOneNode(t *testing.T) { ports := generateUpdatablePorts(portCount, 0) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -921,6 +1013,7 @@ func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -930,6 +1023,10 @@ func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(OneEndpointSlice, 1, 1), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -942,7 +1039,7 @@ func TestDeletedTranslateOneInterestingPortAndOneNode(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -968,6 +1065,7 @@ func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -977,6 +1075,10 @@ func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(OneEndpointSlice, 4, 4), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -989,7 +1091,7 @@ func TestDeletedTranslateManyInterestingPortsAndOneNode(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1014,6 +1116,7 @@ func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1023,6 +1126,10 @@ func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(OneEndpointSlice, 6, 2), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: OneNode, + }, } for name, tc := range testcases { @@ -1035,7 +1142,7 @@ func TestDeletedTranslateManyMixedPortsAndOneNode(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1061,6 +1168,7 @@ func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1070,6 +1178,10 @@ func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(ManyEndpointSlices, 0, 0), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: ManyNodes, + }, } for name, tc := range testcases { @@ -1079,7 +1191,7 @@ func TestDeletedTranslateNoPortsAndManyNodes(t *testing.T) { const expectedEventCount = 0 service := defaultService(tc.serviceType) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1104,6 +1216,7 @@ func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1113,6 +1226,10 @@ func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(ManyEndpointSlices, 1, 0), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: ManyNodes, + }, } for name, tc := range testcases { @@ -1125,7 +1242,7 @@ func TestDeletedTranslateNoInterestingPortsAndManyNodes(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1151,6 +1268,7 @@ func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1160,6 +1278,10 @@ func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(ManyEndpointSlices, 1, 1), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: ManyNodes, + }, } for name, tc := range testcases { @@ -1171,7 +1293,7 @@ func TestDeletedTranslateOneInterestingPortAndManyNodes(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1197,6 +1319,7 @@ func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1206,6 +1329,10 @@ func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { serviceType: v1.ServiceTypeClusterIP, endpoints: generateEndpointSlices(ManyEndpointSlices, 4, 4), }, + "loadBalancer": { + serviceType: v1.ServiceTypeLoadBalancer, + ingresses: ManyNodes, + }, } for name, tc := range testcases { @@ -1217,7 +1344,7 @@ func TestDeletedTranslateManyInterestingPortsAndManyNodes(t *testing.T) { ports := generatePorts(portCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1242,6 +1369,7 @@ func TestDeletedTranslateManyMixedPortsAndManyNodes(t *testing.T) { serviceType v1.ServiceType nodes []*v1.Node endpoints []*discovery.EndpointSlice + ingresses int }{ "nodePort": { serviceType: v1.ServiceTypeNodePort, @@ -1263,7 +1391,7 @@ func TestDeletedTranslateManyMixedPortsAndManyNodes(t *testing.T) { ports := generateUpdatablePorts(portCount, updatablePortCount) service := serviceWithPorts(tc.serviceType, ports) - event := buildDeletedEvent(service) + event := buildDeletedEvent(service, tc.ingresses) translator := NewTranslator(NewFakeEndpointSliceLister(tc.endpoints, nil), NewFakeNodeLister(tc.nodes, nil)) translatedEvents, err := translator.Translate(&event) @@ -1311,21 +1439,27 @@ func serviceWithPorts(serviceType v1.ServiceType, ports []v1.ServicePort) *v1.Se } } -func buildCreatedEvent(service *v1.Service) core.Event { - return buildEvent(core.Created, service) +func buildCreatedEvent(service *v1.Service, ingressCount int) core.Event { + return buildEvent(core.Created, service, ingressCount) } -func buildDeletedEvent(service *v1.Service) core.Event { - return buildEvent(core.Deleted, service) +func buildDeletedEvent(service *v1.Service, ingressCount int) core.Event { + return buildEvent(core.Deleted, service, ingressCount) } -func buildUpdatedEvent(service *v1.Service) core.Event { - return buildEvent(core.Updated, service) +func buildUpdatedEvent(service *v1.Service, ingressCount int) core.Event { + return buildEvent(core.Updated, service, ingressCount) } -func buildEvent(eventType core.EventType, service *v1.Service) core.Event { +func buildEvent(eventType core.EventType, service *v1.Service, ingressCount int) core.Event { event := core.NewEvent(eventType, service) event.Service.Name = "default-service" + ingresses := make([]v1.LoadBalancerIngress, 0, ingressCount) + for i := range ingressCount { + ingress := v1.LoadBalancerIngress{IP: fmt.Sprintf("ipAddress%d", i)} + ingresses = append(ingresses, ingress) + } + event.Service.Status.LoadBalancer.Ingress = ingresses return event } From 82c81a78b5d7541c01de4e6ea788c3eb56b546a6 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Mon, 10 Feb 2025 14:46:31 -0700 Subject: [PATCH 111/136] NLB-6320 Bumped to version 1.1.0 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 3eefcb9..9084fa2 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.0.0 +1.1.0 From 6e48c5de3b04fa9cdc9c2f6d70190ca844bb0170 Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 27 Feb 2025 12:11:03 -0800 Subject: [PATCH 112/136] Remove AZ_LOCATION for stacks We should not be setting this value and instead rely on the tooling defaults. --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 04dffff..7f73a13 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -216,7 +216,6 @@ trigger-e2e: TEST_NLK_CHART_URL: "oci://${DEVOPS_DOCKER_URL_DEFAULT}/nginx-azure-lb/${CI_PROJECT_NAME}/charts/${CI_COMMIT_REF_SLUG}/nginxaas-loadbalancer-kubernetes" TEST_NLK_IMG_TAG: ${CI_COMMIT_SHORT_SHA} TEST_ARGS: "test_nlk.py" - AZ_LOCATION: "West Central US" trigger: project: f5/nginx/nginxazurelb/tools/nlbtest branch: main From 34c3f7cf92bfc3973435ef60e240397c8fc793ba Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 27 Feb 2025 11:11:04 -0700 Subject: [PATCH 113/136] NLB-6293 Updated k8s libraries used by NLK This will help fix vulnerabilities discoved by mend in k8s.io/apiMAChinery-v0.26.0. --- go.mod | 58 +++++++-------- go.sum | 229 ++++++++++++++++++--------------------------------------- 2 files changed, 100 insertions(+), 187 deletions(-) diff --git a/go.mod b/go.mod index f7c3dc8..5943bf6 100644 --- a/go.mod +++ b/go.mod @@ -4,40 +4,38 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.22.6 - -toolchain go1.23.4 +go 1.23.0 require ( github.com/nginx/nginx-plus-go-client/v2 v2.3.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.10.0 - k8s.io/api v0.26.0 - k8s.io/apimachinery v0.26.0 - k8s.io/client-go v0.26.0 + golang.org/x/sync v0.11.0 + k8s.io/api v0.32.2 + k8s.io/apimachinery v0.32.2 + k8s.io/client-go v0.32.2 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -52,27 +50,27 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/x448/float16 v0.8.4 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) replace ( diff --git a/go.sum b/go.sum index 505b7f6..e03c187 100644 --- a/go.sum +++ b/go.sum @@ -1,76 +1,53 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -79,10 +56,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -94,12 +69,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nginx/nginx-plus-go-client/v2 v2.3.0 h1:ciKh1lwadNzUaOGjLcKWu/BGigASxU6p7v/6US71fhA= github.com/nginx/nginx-plus-go-client/v2 v2.3.0/go.mod h1:U7G5pqucUS1V4Uecs1xCsJ9knSsfwqhwu8ZEjoCYnmk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= -github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -107,9 +80,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -124,24 +96,23 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= @@ -149,131 +120,75 @@ go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTV golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= -k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= -k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= -k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= +k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From da3ab085eb7e1d80b7072c80ec34a24b6bd5277d Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 27 Feb 2025 11:12:09 -0700 Subject: [PATCH 114/136] NLB-6293 Added mutex lock around certification's Certificates type This enables concurrency-safe access by multiple goroutines. --- cmd/certificates-test-harness/main.go | 4 +- cmd/tls-config-factory-test-harness/main.go | 40 +++--- internal/authentication/factory_test.go | 130 ++++++++++---------- internal/certification/certificates.go | 61 +++++---- internal/certification/certificates_test.go | 15 ++- 5 files changed, 136 insertions(+), 114 deletions(-) diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go index f3468a9..898c3a3 100644 --- a/cmd/certificates-test-harness/main.go +++ b/cmd/certificates-test-harness/main.go @@ -37,14 +37,14 @@ func run() error { return fmt.Errorf(`error building a Kubernetes client: %w`, err) } - certificates := certification.NewCertificates(ctx, k8sClient) + certificates := certification.NewCertificates(k8sClient, nil) err = certificates.Initialize() if err != nil { return fmt.Errorf(`error occurred initializing certificates: %w`, err) } - go certificates.Run() //nolint:errcheck + go certificates.Run(ctx) //nolint:errcheck <-ctx.Done() return nil diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index 7883d65..638b0bc 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -89,28 +89,28 @@ func buildConfigMap() map[string]TLSConfiguration { } func ssTLSConfig() configuration.Settings { - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) return configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - }, + TLSMode: configuration.SelfSignedTLS, + Certificates: certificates, } } func ssMtlsConfig() configuration.Settings { - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) return configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - }, + TLSMode: configuration.SelfSignedMutualTLS, + Certificates: certificates, } } @@ -121,14 +121,14 @@ func caTLSConfig() configuration.Settings { } func caMtlsConfig() configuration.Settings { - certificates := make(map[string]map[string]core.SecretBytes) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) return configuration.Settings{ - TLSMode: configuration.CertificateAuthorityMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - }, + TLSMode: configuration.CertificateAuthorityMutualTLS, + Certificates: certificates, } } diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go index 6b5fcaf..4777607 100644 --- a/internal/authentication/factory_test.go +++ b/internal/authentication/factory_test.go @@ -37,16 +37,16 @@ func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - CaCertificateSecretKey: CaCertificateSecretKey, - ClientCertificateSecretKey: ClientCertificateSecretKey, - }, + TLSMode: configuration.SelfSignedTLS, + Certificates: certificates, } tlsConfig, err := NewTLSConfig(settings) @@ -73,14 +73,16 @@ func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - }, + TLSMode: configuration.SelfSignedTLS, + Certificates: certificates, } _, err := NewTLSConfig(settings) @@ -95,16 +97,16 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - CaCertificateSecretKey: CaCertificateSecretKey, - ClientCertificateSecretKey: ClientCertificateSecretKey, - }, + TLSMode: configuration.SelfSignedTLS, + Certificates: certificates, } _, err := NewTLSConfig(settings) @@ -119,17 +121,17 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - CaCertificateSecretKey: CaCertificateSecretKey, - ClientCertificateSecretKey: ClientCertificateSecretKey, - }, + TLSMode: configuration.SelfSignedMutualTLS, + Certificates: certificates, } tlsConfig, err := NewTLSConfig(settings) @@ -156,15 +158,17 @@ func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - }, + TLSMode: configuration.SelfSignedMutualTLS, + Certificates: certificates, } _, err := NewTLSConfig(settings) @@ -179,17 +183,17 @@ func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - CaCertificateSecretKey: CaCertificateSecretKey, - ClientCertificateSecretKey: ClientCertificateSecretKey, - }, + TLSMode: configuration.SelfSignedMutualTLS, + Certificates: certificates, } _, err := NewTLSConfig(settings) @@ -232,16 +236,16 @@ func TestTlsFactory_CaTlsMode(t *testing.T) { func TestTlsFactory_CaMtlsMode(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.CertificateAuthorityMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - CaCertificateSecretKey: CaCertificateSecretKey, - ClientCertificateSecretKey: ClientCertificateSecretKey, - }, + TLSMode: configuration.CertificateAuthorityMutualTLS, + Certificates: certificates, } tlsConfig, err := NewTLSConfig(settings) @@ -268,15 +272,17 @@ func TestTlsFactory_CaMtlsMode(t *testing.T) { func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { t.Parallel() - certificates := make(map[string]map[string]core.SecretBytes) - certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + certs := make(map[string]map[string]core.SecretBytes) + certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + + certificates := certification.NewCertificates(nil, certs) + certificates.CaCertificateSecretKey = CaCertificateSecretKey + certificates.ClientCertificateSecretKey = ClientCertificateSecretKey settings := configuration.Settings{ - TLSMode: configuration.CertificateAuthorityMutualTLS, - Certificates: &certification.Certificates{ - Certificates: certificates, - }, + TLSMode: configuration.CertificateAuthorityMutualTLS, + Certificates: certificates, } _, err := NewTLSConfig(settings) diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index c59eb0e..86b9320 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -13,6 +13,7 @@ import ( "context" "fmt" "log/slog" + "sync" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/informers" @@ -34,10 +35,8 @@ const ( ) type Certificates struct { - Certificates map[string]map[string]core.SecretBytes - - // Context is the context used to control the application. - Context context.Context + mu sync.Mutex // guards Certificates + certificates map[string]map[string]core.SecretBytes // CaCertificateSecretKey is the name of the Secret that contains the Certificate Authority certificate. CaCertificateSecretKey string @@ -56,25 +55,32 @@ type Certificates struct { } // NewCertificates factory method that returns a new Certificates object. -func NewCertificates(ctx context.Context, k8sClient kubernetes.Interface) *Certificates { +func NewCertificates( + k8sClient kubernetes.Interface, certificates map[string]map[string]core.SecretBytes, +) *Certificates { return &Certificates{ k8sClient: k8sClient, - Context: ctx, - Certificates: nil, + certificates: certificates, } } // GetCACertificate returns the Certificate Authority certificate. func (c *Certificates) GetCACertificate() core.SecretBytes { - bytes := c.Certificates[c.CaCertificateSecretKey][CertificateKey] + c.mu.Lock() + defer c.mu.Unlock() + + bytes := c.certificates[c.CaCertificateSecretKey][CertificateKey] return bytes } // GetClientCertificate returns the Client certificate and key. func (c *Certificates) GetClientCertificate() (core.SecretBytes, core.SecretBytes) { - keyBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKeyKey] - certificateBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKey] + c.mu.Lock() + defer c.mu.Unlock() + + keyBytes := c.certificates[c.ClientCertificateSecretKey][CertificateKeyKey] + certificateBytes := c.certificates[c.ClientCertificateSecretKey][CertificateKey] return keyBytes, certificateBytes } @@ -85,7 +91,9 @@ func (c *Certificates) Initialize() error { var err error - c.Certificates = make(map[string]map[string]core.SecretBytes) + c.mu.Lock() + c.certificates = make(map[string]map[string]core.SecretBytes) + c.mu.Unlock() informer := c.buildInformer() @@ -100,16 +108,16 @@ func (c *Certificates) Initialize() error { } // Run starts the SharedInformer. -func (c *Certificates) Run() error { +func (c *Certificates) Run(ctx context.Context) error { slog.Info("Certificates::Run") if c.informer == nil { return fmt.Errorf(`initialize must be called before Run`) } - c.informer.Run(c.Context.Done()) + c.informer.Run(ctx.Done()) - <-c.Context.Done() + <-ctx.Done() return nil } @@ -152,17 +160,20 @@ func (c *Certificates) handleAddEvent(obj interface{}) { return } - c.Certificates[secret.Name] = map[string]core.SecretBytes{} + c.mu.Lock() + defer c.mu.Unlock() + + c.certificates[secret.Name] = map[string]core.SecretBytes{} // Input from the secret comes in the form // tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVCVEN... // tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0l... // Where the keys are `tls.crt` and `tls.key` and the values are []byte for k, v := range secret.Data { - c.Certificates[secret.Name][k] = core.SecretBytes(v) + c.certificates[secret.Name][k] = core.SecretBytes(v) } - slog.Debug("Certificates::handleAddEvent", slog.Int("certCount", len(c.Certificates))) + slog.Debug("Certificates::handleAddEvent", slog.Int("certCount", len(c.certificates))) } func (c *Certificates) handleDeleteEvent(obj interface{}) { @@ -174,11 +185,14 @@ func (c *Certificates) handleDeleteEvent(obj interface{}) { return } - if c.Certificates[secret.Name] != nil { - delete(c.Certificates, secret.Name) + c.mu.Lock() + defer c.mu.Unlock() + + if c.certificates[secret.Name] != nil { + delete(c.certificates, secret.Name) } - slog.Debug("Certificates::handleDeleteEvent", slog.Int("certCount", len(c.Certificates))) + slog.Debug("Certificates::handleDeleteEvent", slog.Int("certCount", len(c.certificates))) } func (c *Certificates) handleUpdateEvent(_ interface{}, newValue interface{}) { @@ -190,9 +204,12 @@ func (c *Certificates) handleUpdateEvent(_ interface{}, newValue interface{}) { return } + c.mu.Lock() + defer c.mu.Unlock() + for k, v := range secret.Data { - c.Certificates[secret.Name][k] = v + c.certificates[secret.Name][k] = v } - slog.Debug("Certificates::handleUpdateEvent", slog.Int("certCount", len(c.Certificates))) + slog.Debug("Certificates::handleUpdateEvent", slog.Int("certCount", len(c.certificates))) } diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go index 901964a..7d2363b 100644 --- a/internal/certification/certificates_test.go +++ b/internal/certification/certificates_test.go @@ -23,9 +23,8 @@ const ( func TestNewCertificate(t *testing.T) { t.Parallel() - ctx := context.Background() - certificates := NewCertificates(ctx, nil) + certificates := NewCertificates(nil, nil) if certificates == nil { t.Fatalf(`certificates should not be nil`) @@ -34,7 +33,7 @@ func TestNewCertificate(t *testing.T) { func TestCertificates_Initialize(t *testing.T) { t.Parallel() - certificates := NewCertificates(context.Background(), nil) + certificates := NewCertificates(nil, nil) err := certificates.Initialize() if err != nil { @@ -44,9 +43,9 @@ func TestCertificates_Initialize(t *testing.T) { func TestCertificates_RunWithoutInitialize(t *testing.T) { t.Parallel() - certificates := NewCertificates(context.Background(), nil) + certificates := NewCertificates(nil, nil) - err := certificates.Run() + err := certificates.Run(context.Background()) if err == nil { t.Fatalf(`Expected error`) } @@ -58,7 +57,7 @@ func TestCertificates_RunWithoutInitialize(t *testing.T) { func TestCertificates_EmptyCertificates(t *testing.T) { t.Parallel() - certificates := NewCertificates(context.Background(), nil) + certificates := NewCertificates(nil, nil) err := certificates.Initialize() if err != nil { @@ -86,7 +85,7 @@ func TestCertificates_ExerciseHandlers(t *testing.T) { k8sClient := fake.NewSimpleClientset() - certificates := NewCertificates(ctx, k8sClient) + certificates := NewCertificates(k8sClient, nil) _ = certificates.Initialize() @@ -94,7 +93,7 @@ func TestCertificates_ExerciseHandlers(t *testing.T) { //nolint:govet,staticcheck go func() { - err := certificates.Run() + err := certificates.Run(context.Background()) assert.NoError(t, err, "expected no error running certificates") }() From 8b688a315d6c46b668a172b1be5a571901b8dbfe Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 27 Feb 2025 11:20:49 -0700 Subject: [PATCH 115/136] NLB-6293 Upgraded golangci-lint to v1.64.5 and fixed configuration --- .golangci.yml | 18 +++++++----------- Makefile | 2 +- cmd/nginx-loadbalancer-kubernetes/main.go | 4 ++++ internal/synchronization/synchronizer.go | 3 +++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6cb3587..6f0fddf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,7 +26,6 @@ linters: - misspell - nakedret - prealloc - - exportloopref - stylecheck - unconvert - unparam @@ -38,20 +37,17 @@ linters: run: # 10 minute timeout for analysis timeout: 10m - skip-dirs-use-default: true - skip-dirs: - - .go/pkg/mod - - pkg/spec/api # Generated code - - vendor - - vendor-fork # Specific linter settings linters-settings: gocyclo: # Minimal code complexity to report min-complexity: 16 govet: - # Report shadowed variables - check-shadowing: true + disable-all: true + enable: + # Report shadowed variables + - shadow + misspell: # Correct spellings using locale preferences for US locale: US @@ -63,8 +59,8 @@ linters-settings: # If this list is empty, all structs are tested. # Default: [] include: - - 'gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/pkg/token.TokenID' - - 'gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/internal/dpo/agent/certificates.CertGetRequest' + - "gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/pkg/token.TokenID" + - "gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/internal/dpo/agent/certificates.CertGetRequest" issues: # Exclude configuration diff --git a/Makefile b/Makefile index 4d00656..354bd95 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ tools: @go install gotest.tools/gotestsum@latest @go install golang.org/x/tools/cmd/goimports@latest @go install github.com/jstemmer/go-junit-report@v1.0.0 - @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.57.1 + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.64.5 deps: @go mod download diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 5aba57f..d2d923c 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -133,9 +133,13 @@ func buildKubernetesClient() (*kubernetes.Clientset, error) { return client, nil } +// TODO: NLB-6294 change to use new typed workqueues +// +//nolint:staticcheck //ignore deprecation warnings func buildWorkQueue(settings configuration.WorkQueueSettings) workqueue.RateLimitingInterface { slog.Debug("Watcher::buildSynchronizerWorkQueue") + //nolint:staticcheck //ignore deprecation warnings rateLimiter := workqueue.NewItemExponentialFailureRateLimiter(settings.RateLimiterBase, settings.RateLimiterMax) return workqueue.NewNamedRateLimitingQueue(rateLimiter, settings.Name) } diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 82b3071..3810805 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -50,6 +50,8 @@ type ServiceKey struct { // a Border Client as specified in the Service annotation for the Upstream. // See application/border_client.go and application/application_constants.go for details. type Synchronizer struct { + // TODO: NLB-6294 change to use new typed workqueues + //nolint:staticcheck //ignore deprecation warnings eventQueue workqueue.RateLimitingInterface settings configuration.Settings translator Translator @@ -60,6 +62,7 @@ type Synchronizer struct { // NewSynchronizer creates a new Synchronizer. func NewSynchronizer( settings configuration.Settings, + //nolint:staticcheck //ignore deprecation warnings eventQueue workqueue.RateLimitingInterface, translator Translator, serviceLister corelisters.ServiceLister, From d02645113da736a9d2c7941d8bce6891da26423a Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 27 Feb 2025 15:19:01 -0700 Subject: [PATCH 116/136] NLB-6294 The synchronizer uses a typed rate-limited workqueue The latest versions of the kubernetes libraries recommend using a typed workqueue and this reduces a bit of boilerplate and error handling, because we no longer have to cast the workitems returned by the queue into the desired types. --- cmd/nginx-loadbalancer-kubernetes/main.go | 13 ++++----- internal/synchronization/synchronizer.go | 15 ++-------- internal/synchronization/synchronizer_test.go | 14 ++++----- test/mocks/mock_ratelimitinginterface.go | 29 +++++++++---------- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index d2d923c..43de794 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -133,13 +133,12 @@ func buildKubernetesClient() (*kubernetes.Clientset, error) { return client, nil } -// TODO: NLB-6294 change to use new typed workqueues -// -//nolint:staticcheck //ignore deprecation warnings -func buildWorkQueue(settings configuration.WorkQueueSettings) workqueue.RateLimitingInterface { +func buildWorkQueue(settings configuration.WorkQueueSettings, +) workqueue.TypedRateLimitingInterface[synchronization.ServiceKey] { slog.Debug("Watcher::buildSynchronizerWorkQueue") - //nolint:staticcheck //ignore deprecation warnings - rateLimiter := workqueue.NewItemExponentialFailureRateLimiter(settings.RateLimiterBase, settings.RateLimiterMax) - return workqueue.NewNamedRateLimitingQueue(rateLimiter, settings.Name) + rateLimiter := workqueue.NewTypedItemExponentialFailureRateLimiter[synchronization.ServiceKey]( + settings.RateLimiterBase, settings.RateLimiterMax) + return workqueue.NewTypedRateLimitingQueueWithConfig( + rateLimiter, workqueue.TypedRateLimitingQueueConfig[synchronization.ServiceKey]{Name: settings.Name}) } diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 3810805..50717d7 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -50,9 +50,7 @@ type ServiceKey struct { // a Border Client as specified in the Service annotation for the Upstream. // See application/border_client.go and application/application_constants.go for details. type Synchronizer struct { - // TODO: NLB-6294 change to use new typed workqueues - //nolint:staticcheck //ignore deprecation warnings - eventQueue workqueue.RateLimitingInterface + eventQueue workqueue.TypedRateLimitingInterface[ServiceKey] settings configuration.Settings translator Translator cache *cache @@ -62,8 +60,7 @@ type Synchronizer struct { // NewSynchronizer creates a new Synchronizer. func NewSynchronizer( settings configuration.Settings, - //nolint:staticcheck //ignore deprecation warnings - eventQueue workqueue.RateLimitingInterface, + eventQueue workqueue.TypedRateLimitingInterface[ServiceKey], translator Translator, serviceLister corelisters.ServiceLister, ) (*Synchronizer, error) { @@ -292,13 +289,7 @@ func (s *Synchronizer) handleNextServiceEvent(ctx context.Context) bool { defer s.eventQueue.Done(svc) - key, ok := svc.(ServiceKey) - if !ok { - slog.Warn(`Synchronizer::handleNextServiceEvent: invalid service type`, "service", svc) - return true - } - - s.withRetry(s.handleServiceEvent(ctx, key), key) + s.withRetry(s.handleServiceEvent(ctx, svc), svc) return true } diff --git a/internal/synchronization/synchronizer_test.go b/internal/synchronization/synchronizer_test.go index 7592f5c..c49c718 100644 --- a/internal/synchronization/synchronizer_test.go +++ b/internal/synchronization/synchronizer_test.go @@ -22,7 +22,7 @@ import ( func TestSynchronizer_NewSynchronizer(t *testing.T) { t.Parallel() - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} synchronizer, err := NewSynchronizer( configuration.Settings{}, @@ -43,7 +43,7 @@ func TestSynchronizer_AddEventNoHosts(t *testing.T) { t.Parallel() const expectedEventCount = 0 - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} synchronizer, err := NewSynchronizer( defaultSettings(), @@ -73,7 +73,7 @@ func TestSynchronizer_AddEventOneHost(t *testing.T) { const expectedEventCount = 1 events := buildServerUpdateEvents(1) - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} synchronizer, err := NewSynchronizer( defaultSettings("https://localhost:8080"), @@ -106,7 +106,7 @@ func TestSynchronizer_AddEventManyHosts(t *testing.T) { "https://localhost:8082", } - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} synchronizer, err := NewSynchronizer( defaultSettings(hosts...), @@ -133,7 +133,7 @@ func TestSynchronizer_AddEventsNoHosts(t *testing.T) { t.Parallel() const expectedEventCount = 0 events := buildServerUpdateEvents(4) - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} synchronizer, err := NewSynchronizer( defaultSettings(), @@ -165,7 +165,7 @@ func TestSynchronizer_AddEventsOneHost(t *testing.T) { t.Parallel() const expectedEventCount = 4 events := buildServerUpdateEvents(1) - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} synchronizer, err := NewSynchronizer( defaultSettings("https://localhost:8080"), @@ -195,7 +195,7 @@ func TestSynchronizer_AddEventsManyHosts(t *testing.T) { t.Parallel() const eventCount = 4 events := buildServerUpdateEvents(eventCount) - rateLimiter := &mocks.MockRateLimiter{} + rateLimiter := &mocks.MockRateLimiter[ServiceKey]{} hosts := []string{ "https://localhost:8080", diff --git a/test/mocks/mock_ratelimitinginterface.go b/test/mocks/mock_ratelimitinginterface.go index ee3ccd4..d5da3b7 100644 --- a/test/mocks/mock_ratelimitinginterface.go +++ b/test/mocks/mock_ratelimitinginterface.go @@ -7,51 +7,50 @@ package mocks import "time" -type MockRateLimiter struct { - items []interface{} +type MockRateLimiter[T any] struct { + items []T } -func (m *MockRateLimiter) Add(_ interface{}) { +func (m *MockRateLimiter[T]) Add(_ T) { } -func (m *MockRateLimiter) Len() int { +func (m *MockRateLimiter[T]) Len() int { return len(m.items) } -func (m *MockRateLimiter) Get() (item interface{}, shutdown bool) { +func (m *MockRateLimiter[T]) Get() (item T, shutdown bool) { if len(m.items) > 0 { item = m.items[0] m.items = m.items[1:] return item, false } - return nil, false + return item, false } -func (m *MockRateLimiter) Done(_ interface{}) { +func (m *MockRateLimiter[T]) Done(_ T) { } -func (m *MockRateLimiter) ShutDown() { +func (m *MockRateLimiter[T]) ShutDown() { } -func (m *MockRateLimiter) ShutDownWithDrain() { +func (m *MockRateLimiter[T]) ShutDownWithDrain() { } -func (m *MockRateLimiter) ShuttingDown() bool { +func (m *MockRateLimiter[T]) ShuttingDown() bool { return true } -func (m *MockRateLimiter) AddAfter(item interface{}, _ time.Duration) { +func (m *MockRateLimiter[T]) AddAfter(item T, _ time.Duration) { m.items = append(m.items, item) } -func (m *MockRateLimiter) AddRateLimited(item interface{}) { +func (m *MockRateLimiter[T]) AddRateLimited(item T) { m.items = append(m.items, item) } -func (m *MockRateLimiter) Forget(_ interface{}) { - +func (m *MockRateLimiter[T]) Forget(_ T) { } -func (m *MockRateLimiter) NumRequeues(_ interface{}) int { +func (m *MockRateLimiter[T]) NumRequeues(_ T) int { return 0 } From a2c35961f92f5aefc440e46876fcec34f7bec2e0 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 27 Feb 2025 15:21:53 -0700 Subject: [PATCH 117/136] NLB-6294 Bumped version to 1.1.1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 9084fa2..524cb55 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.1.0 +1.1.1 From 88cefab520d0c518376e612fad9bc791f77a2331 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 28 Feb 2025 09:07:24 -0700 Subject: [PATCH 118/136] NLB-6294 NewTransport clones the default http.DefaultTransport variable Multiple parallel tests were all accessing the same pointer to a single variable for the DefaultTransport in the http package. This was leading to data races in unit tests. --- internal/communication/factory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/communication/factory.go b/internal/communication/factory.go index eec593b..cf1bfcb 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -63,7 +63,7 @@ func NewTLSConfig(settings configuration.Settings) *tls.Config { // NewTransport is a factory method to create a new basic Http Transport. func NewTransport(config *tls.Config) *netHttp.Transport { - transport := netHttp.DefaultTransport.(*netHttp.Transport) + transport := netHttp.DefaultTransport.(*netHttp.Transport).Clone() transport.TLSClientConfig = config return transport From 9e3dde695fe128a90c19e7ed460278f84d06ac7d Mon Sep 17 00:00:00 2001 From: sarna Date: Thu, 13 Mar 2025 10:55:53 -0700 Subject: [PATCH 119/136] Force linux builds for the product We don't really have a need to handle multi arch images yet and all our local workflows are tied to using AKS. While that might change in the future, for the time being, we want to make sure to build and publish linux/amd64 images to match the test envs that we have. --- scripts/docker.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/docker.sh b/scripts/docker.sh index 6208b79..cdfcd82 100755 --- a/scripts/docker.sh +++ b/scripts/docker.sh @@ -13,6 +13,7 @@ build() { --tag "${repo}:${CI_COMMIT_REF_SLUG}" \ --tag "${repo}:${CI_COMMIT_REF_SLUG}-$version" \ --tag "${repo}:${CI_COMMIT_SHORT_SHA}" \ + --platform "linux/amd64" \ -f "${ROOT_DIR}/Dockerfile" . } From 0ae1eb7de6ab55a78d0453f0071f94b88a20d8af Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 15 Apr 2025 13:40:07 -0600 Subject: [PATCH 120/136] Added documents on how to release NLK --- RELEASE.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..7b18e6e --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,50 @@ +# Releasing NLK + +## Releasing a version to our internal dockerhub registry + +- Go to the [NLK repo](https://gitlab.com/f5/nginx/nginxazurelb/nginxaas-loadbalancer-kubernetes). + +- [Create a new tag](https://gitlab.com/f5/nginx/nginxazurelb/nginxaas-loadbalancer-kubernetes/-/tags/new). + +- Give the tag the name of the version to be released, e.g. "v1.1.1". This should match the version in the `version` file at the root of the repo. + +- Under **Create from** select the branch from which the image will be created. + +- Hit **Create tag**. + +## Releasing a version to Azure Marketplace + +This workflow requires Azure Marketplace permissions which few members of NGINXaaS possess (currently Ken, Ashok and Ryan). + +- Complete the steps above to publish the image internally. + +- Go to "Marketplace Offers" + +- Click on "F5 NGINX LoadBalancer for Kubernetes" + +- Under "Plan overview" select the plan which has a "Live" status (this should be "F5 NGINXaaS AKS Extension") + +- On a panel on the left hand side, select "Technical Configuration" + +- A pop up appears. + - Under "Registry" select the "nlbmarketplaceacrprod" option + - Under "Repo" select "marketplace/nginxaas-loadbalancer-kubernetes" + - Under "CNAB Bundle" select the version you wish to publish + +- To complete the publishing of the image click "Add CNAB Button" button on the bottom of the popup. + +- Select "Save draft". + +- This should take you to a "Review and Publish" screen. If the UI seems to stall. Follow steps below. + + - Next to "Marketplace offers" on the top of the screen, select "F5 NGINX Loadbalancer for Kubernetes". + + - Select "Offer overview" from the panel on the left. + + - Next to the heading "F5 NGINX Loadbalancer for Kubernetes | Offer Overview" select "Review and publish" + +- A number of items should appear, but they must include "F5 NGINXaaS AKS extenstion". Leave all items as they are. + +- Then click "Publish". + +- This will take a while. Check in on it after 24 hours. From 97cd5fb546b793adb7fa2b7c7e71a735410c3321 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 13 Mar 2025 16:06:20 -0600 Subject: [PATCH 121/136] Removed synhronization's random functions These functions were being used to create IDs that were not really necessary for the business logic and which were generating security alerts because of weak cryptography techniques. --- internal/core/server_update_event.go | 9 +---- internal/core/server_update_event_test.go | 10 +---- internal/synchronization/rand.go | 39 ------------------- internal/synchronization/synchronizer.go | 11 +++--- internal/synchronization/synchronizer_test.go | 1 - 5 files changed, 8 insertions(+), 62 deletions(-) delete mode 100644 internal/synchronization/rand.go diff --git a/internal/core/server_update_event.go b/internal/core/server_update_event.go index 0bc8868..04d2dc0 100644 --- a/internal/core/server_update_event.go +++ b/internal/core/server_update_event.go @@ -9,14 +9,10 @@ package core // from Events received from the Handler. These are then consumed by the Synchronizer and passed along to // the appropriate BorderClient. type ServerUpdateEvent struct { - // ClientType is the type of BorderClient that should handle this event. This is configured via Service Annotations. // See application_constants.go for the list of supported types. ClientType string - // Id is the unique identifier for this event. - ID string - // NginxHost is the host name of the NGINX Plus instance that should handle this event. NginxHost string @@ -48,11 +44,10 @@ func NewServerUpdateEvent( } } -// ServerUpdateEventWithIDAndHost creates a new ServerUpdateEvent with the specified Id and Host. -func ServerUpdateEventWithIDAndHost(event *ServerUpdateEvent, id string, nginxHost string) *ServerUpdateEvent { +// ServerUpdateEventWithHost creates a new ServerUpdateEvent with the specified Host. +func ServerUpdateEventWithHost(event *ServerUpdateEvent, nginxHost string) *ServerUpdateEvent { return &ServerUpdateEvent{ ClientType: event.ClientType, - ID: id, NginxHost: nginxHost, Type: event.Type, UpstreamName: event.UpstreamName, diff --git a/internal/core/server_update_event_test.go b/internal/core/server_update_event_test.go index 9f36002..3be4702 100644 --- a/internal/core/server_update_event_test.go +++ b/internal/core/server_update_event_test.go @@ -17,19 +17,11 @@ func TestServerUpdateEventWithIdAndHost(t *testing.T) { t.Parallel() event := NewServerUpdateEvent(Created, "upstream", clientType, emptyUpstreamServers) - if event.ID != "" { - t.Errorf("expected empty ID, got %s", event.ID) - } - if event.NginxHost != "" { t.Errorf("expected empty NginxHost, got %s", event.NginxHost) } - eventWithIDAndHost := ServerUpdateEventWithIDAndHost(event, "id", "host") - - if eventWithIDAndHost.ID != "id" { - t.Errorf("expected Id to be 'id', got %s", eventWithIDAndHost.ID) - } + eventWithIDAndHost := ServerUpdateEventWithHost(event, "host") if eventWithIDAndHost.NginxHost != "host" { t.Errorf("expected NginxHost to be 'host', got %s", eventWithIDAndHost.NginxHost) diff --git a/internal/synchronization/rand.go b/internal/synchronization/rand.go deleted file mode 100644 index 6bf58d1..0000000 --- a/internal/synchronization/rand.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package synchronization - -import ( - // Try using crpyto if needed. - "math/rand" - "time" -) - -// charset contains all characters that can be used in random string generation -var charset = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - -// number contains all numbers that can be used in random string generation -var number = []byte("0123456789") - -// alphaNumeric contains all characters and numbers that can be used in random string generation -var alphaNumeric = append(charset, number...) - -// RandomString where n is the length of random string we want to generate -func RandomString(n int) string { - b := make([]byte, n) - for i := range b { - // randomly select 1 character from given charset - b[i] = alphaNumeric[rand.Intn(len(alphaNumeric))] //nolint:gosec - } - return string(b) -} - -// RandomMilliseconds returns a random duration between min and max milliseconds -func RandomMilliseconds(min, max int) time.Duration { - randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec - random := randomizer.Intn(max-min) + min - - return time.Millisecond * time.Duration(random) -} diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 50717d7..80faaa3 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -146,10 +146,9 @@ func (s *Synchronizer) fanOutEventToHosts(event core.ServerUpdateEvents) core.Se var events core.ServerUpdateEvents - for hidx, host := range s.settings.NginxPlusHosts { - for eidx, event := range event { - id := fmt.Sprintf(`[%d:%d]-[%s]-[%s]-[%s]`, hidx, eidx, RandomString(12), event.UpstreamName, host) - updatedEvent := core.ServerUpdateEventWithIDAndHost(event, id, host) + for _, host := range s.settings.NginxPlusHosts { + for _, event := range event { + updatedEvent := core.ServerUpdateEventWithHost(event, host) events = append(events, updatedEvent) } @@ -237,7 +236,7 @@ func (s *Synchronizer) handleServiceEvent(ctx context.Context, key ServiceKey) ( // handleCreatedUpdatedEvent handles events of type Created or Updated. func (s *Synchronizer) handleCreatedUpdatedEvent(ctx context.Context, serverUpdateEvent *core.ServerUpdateEvent) error { - slog.Debug(`Synchronizer::handleCreatedUpdatedEvent`, "eventID", serverUpdateEvent.ID) + slog.Debug(`Synchronizer::handleCreatedUpdatedEvent`) var err error @@ -255,7 +254,7 @@ func (s *Synchronizer) handleCreatedUpdatedEvent(ctx context.Context, serverUpda // handleDeletedEvent handles events of type Deleted. func (s *Synchronizer) handleDeletedEvent(ctx context.Context, serverUpdateEvent *core.ServerUpdateEvent) error { - slog.Debug(`Synchronizer::handleDeletedEvent`, "eventID", serverUpdateEvent.ID) + slog.Debug(`Synchronizer::handleDeletedEvent`) var err error diff --git a/internal/synchronization/synchronizer_test.go b/internal/synchronization/synchronizer_test.go index c49c718..ba2253b 100644 --- a/internal/synchronization/synchronizer_test.go +++ b/internal/synchronization/synchronizer_test.go @@ -244,7 +244,6 @@ func buildServerUpdateEvents(count int) core.ServerUpdateEvents { events := make(core.ServerUpdateEvents, count) for i := 0; i < count; i++ { events[i] = &core.ServerUpdateEvent{ - ID: fmt.Sprintf("id-%v", i), NginxHost: "https://localhost:8080", Type: 0, UpstreamName: "", From 927ed461e82d73ee5653fa6a71f8d9cc7ed1dfb8 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 21 Mar 2025 14:46:22 -0600 Subject: [PATCH 122/136] NGINX plus http client rejects requests with too many headers The http client is processing requests created by the nginx plus client library, and that library should always include a sensible number of headers. But the lack of change on the number of headers was causing security vulnerability flags to be raised over denial of service resource exhaustion attacks. --- internal/communication/roundtripper.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/communication/roundtripper.go b/internal/communication/roundtripper.go index 58de6f0..1dbaf5b 100644 --- a/internal/communication/roundtripper.go +++ b/internal/communication/roundtripper.go @@ -6,10 +6,13 @@ package communication import ( + "errors" "net/http" "strings" ) +const maxHeaders = 1000 + // RoundTripper is a simple type that wraps the default net/communication RoundTripper to add additional headers. type RoundTripper struct { Headers []string @@ -26,6 +29,10 @@ func NewRoundTripper(headers []string, transport *http.Transport) *RoundTripper // RoundTrip This simply adds our default headers to the request before passing it on to the default RoundTripper. func (roundTripper *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if len(req.Header) > maxHeaders { + return nil, errors.New("request includes too many headers") + } + newRequest := new(http.Request) *newRequest = *req newRequest.Header = make(http.Header, len(req.Header)) From ab78f210bf04a865fd5ce6bbc8bc6d168eff3e0c Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 21 Mar 2025 14:46:49 -0600 Subject: [PATCH 123/136] Upgraded go to 1.23.8 and golang.org/x/sync to v0.12.0 --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 5943bf6..3fd8dff 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.23.0 +go 1.23.8 require ( github.com/nginx/nginx-plus-go-client/v2 v2.3.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - golang.org/x/sync v0.11.0 + golang.org/x/sync v0.12.0 k8s.io/api v0.32.2 k8s.io/apimachinery v0.32.2 k8s.io/client-go v0.32.2 diff --git a/go.sum b/go.sum index e03c187..9169172 100644 --- a/go.sum +++ b/go.sum @@ -135,8 +135,8 @@ golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From afb802e4cd8e541ba8db28125693b9524f05e343 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Fri, 21 Mar 2025 14:46:49 -0600 Subject: [PATCH 124/136] Bumped version to 1.1.2 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 524cb55..45a1b3f 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.1.1 +1.1.2 From 55f50e2f7094a3492d9520b751bbaaef754d9892 Mon Sep 17 00:00:00 2001 From: Aniruddh Kuthiala Date: Tue, 22 Apr 2025 12:51:31 -0600 Subject: [PATCH 125/136] NLB-6372: enable scanning on merge requests, merged to main and tags with v* --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7f73a13..0ebf127 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -154,7 +154,7 @@ whitesource-scan: rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' when: never - - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + - !reference [.whitesource-template-go, rules] artifacts: when: always paths: @@ -178,7 +178,6 @@ checkmarx-scan: CX_SOURCES: "." CX_FLOW_ZIP_EXCLUDE: "" needs: [] - allow_failure: true # https://docs.gitlab.com/ee/user/application_security/container_scanning/ container_scanning: From 8765185f5771a83f0370457a894886ff93ab0528 Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 19 May 2025 16:08:00 -0700 Subject: [PATCH 126/136] Enable dependency caching --- .gitlab-ci.yml | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0ebf127..41c7290 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,23 +33,22 @@ workflow: .import-devops-core-services: &import-devops-core-services | source ${CI_PROJECT_DIR}/.devops.sh -# Disabld due to: NLB-5341 -# .go-cache: -# variables: -# GOPATH: $CI_PROJECT_DIR/.go -# cache: -# key: -# files: -# - go.mod -# - go.sum -# paths: -# - .go/pkg/mod/ +.go-cache: + variables: + GOPATH: $CI_PROJECT_DIR/.go + cache: + key: + files: + - go.mod + - go.sum + paths: + - .go/pkg/mod/ -# .go-cache-readonly: -# extends: -# - .go-cache -# cache: -# policy: pull +.go-cache-readonly: + extends: + - .go-cache + cache: + policy: pull .golang-private: &golang-private - | @@ -66,7 +65,7 @@ workflow: image: $DEVTOOLS_IMG extends: - .default-runner-large - # - .go-cache-readonly + - .go-cache-readonly script: - *golang-private - time make test @@ -85,7 +84,7 @@ lint + unit-test + build: image: $DEVTOOLS_IMG extends: - .devops-docker-cicd-large - # - .go-cache + - .go-cache script: - *golang-private - | @@ -143,7 +142,7 @@ whitesource-scan: stage: lint+test+build extends: - .default-runner - # - .go-cache + - .go-cache - .whitesource-template-go variables: PRODUCT_PREFIX: "n4a" From 1633c2151fd69d07ef5af16202e8f77dbc696b9d Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 19 May 2025 16:11:53 -0700 Subject: [PATCH 127/136] Skip go-cache while linting Go cache in the CI is seeded in the project working directory. We should skip the mod cache from lint/formatting as it's upstream code and there are high chances of the linting failing as upstream lint rules != our lint rules. --- .golangci.yml | 2 ++ Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 6f0fddf..268e39f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -63,6 +63,8 @@ linters-settings: - "gitlab.com/f5/nginx/nginxazurelb/azure-resource-provider/internal/dpo/agent/certificates.CertGetRequest" issues: + exclude-dirs: + - .go/pkg/mod # Exclude configuration exclude-rules: # Exclude gochecknoinits and gosec from running on tests files diff --git a/Makefile b/Makefile index 354bd95..dd02b98 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ fmt: @find . -type f -name "*.go" -exec goimports -e -w {} \+ lint: - @find . -type f -name "*.go" -exec goimports -e -w {} \+ + @find . -type f -not -path "./.go/pkg/mod/*" -name "*.go" -exec goimports -e -w {} \+ @golangci-lint run -v ./... helm-lint: From c992734fe4482d8cba80b24b58644ffceb3d514c Mon Sep 17 00:00:00 2001 From: sarna Date: Mon, 19 May 2025 16:19:06 -0700 Subject: [PATCH 128/136] Exit on diff failures from goimports fixes --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index dd02b98..c45e2cf 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ fmt: lint: @find . -type f -not -path "./.go/pkg/mod/*" -name "*.go" -exec goimports -e -w {} \+ + @ git diff --exit-code @golangci-lint run -v ./... helm-lint: From 3ab6be15c408c6a1a0509941f6fdefd95be86752 Mon Sep 17 00:00:00 2001 From: Aniruddh Kuthiala Date: Fri, 30 May 2025 10:37:28 -0600 Subject: [PATCH 129/136] unit test flake fix and go version upgrade to 1.24.3 --- go.mod | 35 +++++++++-------- go.sum | 73 +++++++++++++++++++----------------- internal/probation/server.go | 12 +++++- version | 2 +- 4 files changed, 66 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 3fd8dff..c09a4b4 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,16 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.23.8 +go 1.24.3 require ( github.com/nginx/nginx-plus-go-client/v2 v2.3.0 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 golang.org/x/sync v0.12.0 - k8s.io/api v0.32.2 - k8s.io/apimachinery v0.32.2 - k8s.io/client-go v0.32.2 + k8s.io/api v0.33.1 + k8s.io/apimachinery v0.33.1 + k8s.io/client-go v0.33.1 ) require ( @@ -26,10 +26,8 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -54,22 +52,23 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9169172..80d0e9d 100644 --- a/go.sum +++ b/go.sum @@ -25,16 +25,12 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -80,8 +76,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -99,14 +95,16 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -115,6 +113,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -128,10 +128,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -140,16 +140,16 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -160,8 +160,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -174,21 +174,24 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= -k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= -k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= -k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= -k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= +k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= +k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/probation/server.go b/internal/probation/server.go index 9520fc7..c3328c7 100644 --- a/internal/probation/server.go +++ b/internal/probation/server.go @@ -8,6 +8,7 @@ package probation import ( "fmt" "log/slog" + "net" "net/http" "time" ) @@ -58,10 +59,17 @@ func (hs *HealthServer) Start() { mux.HandleFunc("/livez", hs.HandleLive) mux.HandleFunc("/readyz", hs.HandleReady) mux.HandleFunc("/startupz", hs.HandleStartup) - hs.httpServer = &http.Server{Addr: address, Handler: mux, ReadTimeout: 2 * time.Second} + + listener, err := net.Listen("tcp", address) + if err != nil { + slog.Error("failed to listen", "error", err) + return + } + + hs.httpServer = &http.Server{Handler: mux, ReadTimeout: 2 * time.Second} go func() { - if err := hs.httpServer.ListenAndServe(); err != nil { + if err := hs.httpServer.Serve(listener); err != nil { slog.Error("unable to start probe listener", "address", hs.httpServer.Addr, "error", err) } }() diff --git a/version b/version index 45a1b3f..781dcb0 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.1.2 +1.1.3 From f67d455811373caa6e2bf1d248fb35900d053706 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 26 Jun 2025 15:32:04 -0600 Subject: [PATCH 130/136] Fixed up helm templating for nginx-hosts param to support multiple nginx hosts In order for the nginx-hosts yaml field to be parsed correctly by viper the template needs to: 1. not put double quotes around the value (this causes viper to interpret it as a string) 2. render it as a JSON array rather than a go representation of a slice. --- charts/nlk/templates/nlk-configmap.yaml | 2 +- internal/configuration/configuration_test.go | 112 +++++++++++++----- .../testdata/multiple-nginx-hosts.yaml | 11 ++ .../testdata/one-nginx-host.yaml | 11 ++ internal/configuration/testdata/test.yaml | 11 -- version | 2 +- 6 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 internal/configuration/testdata/multiple-nginx-hosts.yaml create mode 100644 internal/configuration/testdata/one-nginx-host.yaml delete mode 100644 internal/configuration/testdata/test.yaml diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml index 38475a9..0b6db57 100644 --- a/charts/nlk/templates/nlk-configmap.yaml +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -9,7 +9,7 @@ data: log-level: "{{ . }}" {{- end }} {{- with .Values.nlk.config.nginxHosts }} - nginx-hosts: "{{ . }}" + nginx-hosts: {{ toJson . }} {{- end }} tls-mode: "{{ .Values.nlk.config.tls.mode }}" {{- with .Values.nlk.config.serviceAnnotationMatch }} diff --git a/internal/configuration/configuration_test.go b/internal/configuration/configuration_test.go index 9694989..fc41974 100644 --- a/internal/configuration/configuration_test.go +++ b/internal/configuration/configuration_test.go @@ -12,41 +12,91 @@ import ( func TestConfiguration(t *testing.T) { t.Parallel() - expectedSettings := configuration.Settings{ - LogLevel: "warn", - NginxPlusHosts: []string{"https://10.0.0.1:9000/api"}, - TLSMode: configuration.NoTLS, - Certificates: &certification.Certificates{ - CaCertificateSecretKey: "fakeCAKey", - ClientCertificateSecretKey: "fakeCertKey", - }, - Handler: configuration.HandlerSettings{ - RetryCount: 5, - Threads: 1, - WorkQueueSettings: configuration.WorkQueueSettings{ - RateLimiterBase: time.Second * 2, - RateLimiterMax: time.Second * 60, - Name: "nlk-handler", + + tests := map[string]struct { + testFile string + expectedSettings configuration.Settings + }{ + "one nginx plus host": { + testFile: "one-nginx-host", + expectedSettings: configuration.Settings{ + LogLevel: "warn", + NginxPlusHosts: []string{"https://10.0.0.1:9000/api"}, + TLSMode: configuration.NoTLS, + Certificates: &certification.Certificates{ + CaCertificateSecretKey: "fakeCAKey", + ClientCertificateSecretKey: "fakeCertKey", + }, + Handler: configuration.HandlerSettings{ + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-handler", + }, + }, + Synchronizer: configuration.SynchronizerSettings{ + MaxMillisecondsJitter: 750, + MinMillisecondsJitter: 250, + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-synchronizer", + }, + }, + Watcher: configuration.WatcherSettings{ + ResyncPeriod: 0, + ServiceAnnotation: "fakeServiceMatch", + }, }, }, - Synchronizer: configuration.SynchronizerSettings{ - MaxMillisecondsJitter: 750, - MinMillisecondsJitter: 250, - RetryCount: 5, - Threads: 1, - WorkQueueSettings: configuration.WorkQueueSettings{ - RateLimiterBase: time.Second * 2, - RateLimiterMax: time.Second * 60, - Name: "nlk-synchronizer", + "multiple nginx plus hosts": { + testFile: "multiple-nginx-hosts", + expectedSettings: configuration.Settings{ + LogLevel: "warn", + NginxPlusHosts: []string{"https://10.0.0.1:9000/api", "https://10.0.0.2:9000/api"}, + TLSMode: configuration.NoTLS, + Certificates: &certification.Certificates{ + CaCertificateSecretKey: "fakeCAKey", + ClientCertificateSecretKey: "fakeCertKey", + }, + Handler: configuration.HandlerSettings{ + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-handler", + }, + }, + Synchronizer: configuration.SynchronizerSettings{ + MaxMillisecondsJitter: 750, + MinMillisecondsJitter: 250, + RetryCount: 5, + Threads: 1, + WorkQueueSettings: configuration.WorkQueueSettings{ + RateLimiterBase: time.Second * 2, + RateLimiterMax: time.Second * 60, + Name: "nlk-synchronizer", + }, + }, + Watcher: configuration.WatcherSettings{ + ResyncPeriod: 0, + ServiceAnnotation: "fakeServiceMatch", + }, }, }, - Watcher: configuration.WatcherSettings{ - ResyncPeriod: 0, - ServiceAnnotation: "fakeServiceMatch", - }, } - settings, err := configuration.Read("test", "./testdata") - require.NoError(t, err) - require.Equal(t, expectedSettings, settings) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + settings, err := configuration.Read(tc.testFile, "./testdata") + require.NoError(t, err) + require.Equal(t, tc.expectedSettings, settings) + }) + } } diff --git a/internal/configuration/testdata/multiple-nginx-hosts.yaml b/internal/configuration/testdata/multiple-nginx-hosts.yaml new file mode 100644 index 0000000..2235c1a --- /dev/null +++ b/internal/configuration/testdata/multiple-nginx-hosts.yaml @@ -0,0 +1,11 @@ +ca-certificate: "fakeCAKey" +client-certificate: "fakeCertKey" +log-level: "warn" +nginx-hosts: ["https://10.0.0.1:9000/api", "https://10.0.0.2:9000/api"] +tls-mode: "no-tls" +service-annotation-match: "fakeServiceMatch" +creationTimestamp: "2024-09-04T17:59:20Z" +name: "nlk-config" +namespace: "nlk" +resourceVersion: "5909" +uid: "66d49974-49d6-4ad8-8135-dcebda7b5c9e" diff --git a/internal/configuration/testdata/one-nginx-host.yaml b/internal/configuration/testdata/one-nginx-host.yaml new file mode 100644 index 0000000..f05d81e --- /dev/null +++ b/internal/configuration/testdata/one-nginx-host.yaml @@ -0,0 +1,11 @@ +ca-certificate: "fakeCAKey" +client-certificate: "fakeCertKey" +log-level: "warn" +nginx-hosts: "https://10.0.0.1:9000/api" +tls-mode: "no-tls" +service-annotation-match: "fakeServiceMatch" +creationTimestamp: "2024-09-04T17:59:20Z" +name: "nlk-config" +namespace: "nlk" +resourceVersion: "5909" +uid: "66d49974-49d6-4ad8-8135-dcebda7b5c9e" diff --git a/internal/configuration/testdata/test.yaml b/internal/configuration/testdata/test.yaml deleted file mode 100644 index 717dcdb..0000000 --- a/internal/configuration/testdata/test.yaml +++ /dev/null @@ -1,11 +0,0 @@ -ca-certificate: fakeCAKey -client-certificate: fakeCertKey -log-level: warn -nginx-hosts: https://10.0.0.1:9000/api -tls-mode: no-tls -service-annotation-match: fakeServiceMatch -creationTimestamp: "2024-09-04T17:59:20Z" -name: nlk-config -namespace: nlk -resourceVersion: "5909" -uid: 66d49974-49d6-4ad8-8135-dcebda7b5c9e diff --git a/version b/version index 781dcb0..65087b4 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.1.3 +1.1.4 From c4163b2e14dbad4c5da6534e8875c3296562cd28 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Mon, 30 Jun 2025 14:20:12 -0600 Subject: [PATCH 131/136] NLB-6295 Simplified user configuration of TLS modes The biggest change here is to remove most the TLS modes to enable mTLS and self-signed certificates. Product decided that this was too complex and there was not enough user demand for most of these options. We decided to pare down the code and remove tests that were no longer well maintained. The remaining configuration allows users to toggle a single switch: whether to make the http client verify the NGINX host's certificate chain and host name if https is being used. If the user wishes to enable https with self-signed certs they can use the "skip-verify-tls" setting to allow this. The default behavior is to perform this verification. We are maintaining the deprecated "no-tls" and "ca-tls" inputs for NGINXaaS backwards comptability reasons. The "no-tls" setting name was highly misleading, because all it did was disable TLS verification: it DID NOT disable TLS altogether in https mode. Similarly, the "ca-tls" setting did not enable TLS itself. TLS is enabled by default when the URL of the NGINX host includes the https protocol. The user setting merely enforced the verification of the certificate chain and host as described above. --- cmd/certificates-test-harness/doc.go | 11 - cmd/certificates-test-harness/main.go | 81 ---- cmd/tls-config-factory-test-harness/doc.go | 1 - cmd/tls-config-factory-test-harness/main.go | 236 ---------- internal/authentication/doc.go | 10 - internal/authentication/factory.go | 123 ----- internal/authentication/factory_test.go | 442 ------------------ internal/certification/certificates.go | 215 --------- internal/certification/certificates_test.go | 232 --------- internal/certification/doc.go | 10 - internal/communication/factory.go | 33 +- internal/communication/factory_test.go | 15 +- internal/communication/roundtripper_test.go | 12 +- internal/configuration/configuration_test.go | 60 ++- internal/configuration/settings.go | 39 +- .../testdata/one-nginx-host.yaml | 1 - internal/configuration/tlsmodes.go | 48 +- internal/configuration/tlsmodes_test.go | 76 --- internal/core/secret_bytes.go | 21 - internal/core/secret_bytes_test.go | 33 -- internal/synchronization/synchronizer.go | 3 +- 21 files changed, 95 insertions(+), 1607 deletions(-) delete mode 100644 cmd/certificates-test-harness/doc.go delete mode 100644 cmd/certificates-test-harness/main.go delete mode 100644 cmd/tls-config-factory-test-harness/doc.go delete mode 100644 cmd/tls-config-factory-test-harness/main.go delete mode 100644 internal/authentication/doc.go delete mode 100644 internal/authentication/factory.go delete mode 100644 internal/authentication/factory_test.go delete mode 100644 internal/certification/certificates.go delete mode 100644 internal/certification/certificates_test.go delete mode 100644 internal/certification/doc.go delete mode 100644 internal/configuration/tlsmodes_test.go delete mode 100644 internal/core/secret_bytes.go delete mode 100644 internal/core/secret_bytes_test.go diff --git a/cmd/certificates-test-harness/doc.go b/cmd/certificates-test-harness/doc.go deleted file mode 100644 index 2d76fd5..0000000 --- a/cmd/certificates-test-harness/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -/* -Package certificates_test_harness includes functionality boostrap -and test the certification.Certificates implplementation. -*/ - -package main diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go deleted file mode 100644 index 898c3a3..0000000 --- a/cmd/certificates-test-harness/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "log/slog" - "os" - "path/filepath" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" -) - -func main() { - handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}) - logger := slog.New(handler) - slog.SetDefault(logger) - err := run() - if err != nil { - slog.Error(err.Error()) - os.Exit(1) - } -} - -func run() error { - slog.Info("certificates-test-harness::run") - - ctx := context.Background() - var err error - - k8sClient, err := buildKubernetesClient() - if err != nil { - return fmt.Errorf(`error building a Kubernetes client: %w`, err) - } - - certificates := certification.NewCertificates(k8sClient, nil) - - err = certificates.Initialize() - if err != nil { - return fmt.Errorf(`error occurred initializing certificates: %w`, err) - } - - go certificates.Run(ctx) //nolint:errcheck - - <-ctx.Done() - return nil -} - -func buildKubernetesClient() (*kubernetes.Clientset, error) { - slog.Debug("Watcher::buildKubernetesClient") - - var kubeconfig *string - var k8sConfig *rest.Config - - k8sConfig, err := rest.InClusterConfig() - if errors.Is(err, rest.ErrNotInCluster) { - if home := homedir.HomeDir(); home != "" { - path := filepath.Join(home, ".kube", "config") - kubeconfig = &path - - k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) - if err != nil { - return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) - } - } else { - return nil, fmt.Errorf(`not running in a Cluster: %w`, err) - } - } else if err != nil { - return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) - } - - client, err := kubernetes.NewForConfig(k8sConfig) - if err != nil { - return nil, fmt.Errorf(`error occurred creating a client: %w`, err) - } - return client, nil -} diff --git a/cmd/tls-config-factory-test-harness/doc.go b/cmd/tls-config-factory-test-harness/doc.go deleted file mode 100644 index 06ab7d0..0000000 --- a/cmd/tls-config-factory-test-harness/doc.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go deleted file mode 100644 index 638b0bc..0000000 --- a/cmd/tls-config-factory-test-harness/main.go +++ /dev/null @@ -1,236 +0,0 @@ -package main - -import ( - "bufio" - "log/slog" - "os" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" -) - -const ( - CaCertificateSecretKey = "nlk-tls-ca-secret" - ClientCertificateSecretKey = "nlk-tls-client-secret" -) - -type TLSConfiguration struct { - Description string - Settings configuration.Settings -} - -func main() { - handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}) - logger := slog.New(handler) - slog.SetDefault(logger) - - configurations := buildConfigMap() - - for name, settings := range configurations { - slog.Info("\033[H\033[2J") - - slog.Info("\n\n\t*** Building TLS config\n\n", "name", name) - - tlsConfig, err := authentication.NewTLSConfig(settings.Settings) - if err != nil { - panic(err) - } - - rootCaCount := 0 - certificateCount := 0 - - if tlsConfig.RootCAs != nil { - rootCaCount = len(tlsConfig.RootCAs.Subjects()) //nolint:staticcheck - } - - if tlsConfig.Certificates != nil { - certificateCount = len(tlsConfig.Certificates) - } - - slog.Info("Successfully built TLS config", - "description", settings.Description, - "rootCaCount", rootCaCount, - "certificateCount", certificateCount, - ) - - _, _ = bufio.NewReader(os.Stdin).ReadBytes('\n') - } - - slog.Info("\033[H\033[2J") - slog.Info("\n\n\t*** All done! ***\n\n") -} - -func buildConfigMap() map[string]TLSConfiguration { - configurations := make(map[string]TLSConfiguration) - - configurations["ss-tls"] = TLSConfiguration{ - Description: "Self-signed TLS requires just a CA certificate", - Settings: ssTLSConfig(), - } - - configurations["ss-mtls"] = TLSConfiguration{ - Description: "Self-signed mTLS requires a CA certificate and a client certificate", - Settings: ssMtlsConfig(), - } - - configurations["ca-tls"] = TLSConfiguration{ - Description: "CA TLS requires no certificates", - Settings: caTLSConfig(), - } - - configurations["ca-mtls"] = TLSConfiguration{ - Description: "CA mTLS requires a client certificate", - Settings: caMtlsConfig(), - } - - return configurations -} - -func ssTLSConfig() configuration.Settings { - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - - return configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: certificates, - } -} - -func ssMtlsConfig() configuration.Settings { - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - - return configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: certificates, - } -} - -func caTLSConfig() configuration.Settings { - return configuration.Settings{ - TLSMode: configuration.CertificateAuthorityTLS, - } -} - -func caMtlsConfig() configuration.Settings { - certs := make(map[string]map[string]core.SecretBytes) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - - return configuration.Settings{ - TLSMode: configuration.CertificateAuthorityMutualTLS, - Certificates: certificates, - } -} - -func caCertificatePEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIDTzCCAjcCFA4Zdj3E9TdjOP48eBRDGRLfkj7CMA0GCSqGSIb3DQEBCwUAMGQx -CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0 -dGxlMQ4wDAYDVQQKDAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFu -Y2VzMB4XDTIzMDkyOTE3MTY1MVoXDTIzMTAyOTE3MTY1MVowZDELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxDjAMBgNV -BAoMBU5HSU5YMR4wHAYDVQQLDBVDb21tdW5pdHkgJiBBbGxpYW5jZXMwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwlI4ZvJ/6hvqULFVL+1ZSRDTPQ48P -umehJhPz6xPhC9UkeTe2FZxm2Rsi1I5QXm/bTG2OcX775jgXzae9NQjctxwrz4Ks -LOWUvRkkfhQR67xk0Noux76/9GWGnB+Fapn54tlWql6uHQfOu1y7MCRkZ27zHbkk -lq4Oa2RmX8rIyECWgbTyL0kETBVJU8bYORQ5JjhRlz08inq3PggY8blrehIetrWN -dw+gzcqdvAI2uSCodHTHM/77KipnYmPiSiDjSDRlXdxTG8JnyIB78IoH/sw6RyBm -CvVa3ytvKziXAvbBoXq5On5WmMRF97p/MmBc53ExMuDZjA4fisnViS0PAgMBAAEw -DQYJKoZIhvcNAQELBQADggEBAJeoa2P59zopLjBInx/DnWn1N1CmFLb0ejKxG2jh -cOw15Sx40O0XrtrAto38iu4R/bkBeNCSUILlT+A3uYDila92Dayvls58WyIT3meD -G6+Sx/QDF69+4AXpVy9mQ+hxcofpFA32+GOMXwmk2OrAcdSkkGSBhZXgvTpQ64dl -xSiQ5EQW/K8LoBoEOXfjIZJNPORgKn5MI09AY7/47ycKDKTUU2yO8AtIHYKttw0x -kfIg7QOdo1F9IXVpGjJI7ynyrgsCEYxMoDyH42Dq84eKgrUFLEXemEz8hgdFgK41 -0eUYhAtzWHbRPBp+U/34CQoZ5ChNFp2YipvtXrzKE8KLkuM= ------END CERTIFICATE----- -` -} - -func clientCertificatePEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL -BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM -CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu -dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT -MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK -DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk -H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI -ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto -oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 -Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA -TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 -MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx -g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM -Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy -MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ -KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt -eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK -ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE -Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH -R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k -Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= ------END CERTIFICATE----- -` -} - -// clientKeyPEM returns a PEM-encoded client key. -// Note: The key is self-signed and generated explicitly for tests, -// it is not used anywhere else. -func clientKeyPEM() string { - return ` ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub -NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 -/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g -bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek -sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 -R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde -wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 -CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 -rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg -NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e -/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB -ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md -MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ -Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT -FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe -OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 -X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE -1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex -JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig -iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp -r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy -SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB -OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ -sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC -mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 -z/3KkMx4uqJXZyvQrmkolSg= ------END PRIVATE KEY----- -` -} - -func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string]core.SecretBytes { - return map[string]core.SecretBytes{ - certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), - certification.CertificateKeyKey: core.SecretBytes([]byte(keyPEM)), - } -} - -func buildCaCertificateEntry(certificatePEM string) map[string]core.SecretBytes { - return map[string]core.SecretBytes{ - certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), - } -} diff --git a/internal/authentication/doc.go b/internal/authentication/doc.go deleted file mode 100644 index 109255e..0000000 --- a/internal/authentication/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -/* -Package authentication includes functionality to secure communications between NLK and NGINX Plus hosts. -*/ - -package authentication diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go deleted file mode 100644 index c0664f6..0000000 --- a/internal/authentication/factory.go +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - * - * Factory for creating tls.Config objects based on the provided `tls-mode`. - */ - -package authentication - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - "fmt" - "log/slog" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" -) - -func NewTLSConfig(settings configuration.Settings) (*tls.Config, error) { - slog.Debug("authentication::NewTLSConfig Creating TLS config", "mode", settings.TLSMode) - switch settings.TLSMode { - - case configuration.NoTLS: - return buildBasicTLSConfig(true), nil - - case configuration.SelfSignedTLS: // needs ca cert - return buildSelfSignedTLSConfig(settings.Certificates) - - case configuration.SelfSignedMutualTLS: // needs ca cert and client cert - return buildSelfSignedMtlsConfig(settings.Certificates) - - case configuration.CertificateAuthorityTLS: // needs nothing - return buildBasicTLSConfig(false), nil - - case configuration.CertificateAuthorityMutualTLS: // needs client cert - return buildCATLSConfig(settings.Certificates) - - default: - return nil, fmt.Errorf("unknown TLS mode: %s", settings.TLSMode) - } -} - -func buildSelfSignedTLSConfig(certificates *certification.Certificates) (*tls.Config, error) { - slog.Debug("authentication::buildSelfSignedTlsConfig Building self-signed TLS config") - certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) - if err != nil { - return nil, err - } - - //nolint:gosec - return &tls.Config{ - InsecureSkipVerify: false, - RootCAs: certPool, - }, nil -} - -func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - slog.Debug("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config") - certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) - if err != nil { - return nil, err - } - - certificate, err := buildCertificates(certificates.GetClientCertificate()) - if err != nil { - return nil, err - } - slog.Debug("buildSelfSignedMtlsConfig Certificate", "certificate", certificate) - - //nolint:gosec - return &tls.Config{ - InsecureSkipVerify: false, - RootCAs: certPool, - ClientAuth: tls.RequireAndVerifyClientCert, - Certificates: []tls.Certificate{certificate}, - }, nil -} - -func buildBasicTLSConfig(skipVerify bool) *tls.Config { - slog.Debug("authentication::buildBasicTLSConfig", slog.Bool("skipVerify", skipVerify)) - return &tls.Config{ - InsecureSkipVerify: skipVerify, //nolint:gosec - } -} - -func buildCATLSConfig(certificates *certification.Certificates) (*tls.Config, error) { - slog.Debug("authentication::buildCATLSConfig") - certificate, err := buildCertificates(certificates.GetClientCertificate()) - if err != nil { - return nil, err - } - - //nolint:gosec - return &tls.Config{ - InsecureSkipVerify: false, - Certificates: []tls.Certificate{certificate}, - }, nil -} - -func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { - slog.Debug("authentication::buildCertificates") - return tls.X509KeyPair(certificatePEM, privateKeyPEM) -} - -func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { - slog.Debug("authentication::buildCaCertificatePool") - block, _ := pem.Decode(caCert) - if block == nil { - return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") - } - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, fmt.Errorf("error parsing certificate: %w", err) - } - - caCertPool := x509.NewCertPool() - caCertPool.AddCert(cert) - - return caCertPool, nil -} diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go deleted file mode 100644 index 4777607..0000000 --- a/internal/authentication/factory_test.go +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package authentication - -import ( - "testing" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" -) - -const ( - CaCertificateSecretKey = "nlk-tls-ca-secret" - ClientCertificateSecretKey = "nlk-tls-client-secret" -) - -func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { - t.Parallel() - - tlsConfig, err := NewTLSConfig(configuration.Settings{}) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - - if tlsConfig == nil { - t.Fatalf(`tlsConfig should not be nil`) - } - - if tlsConfig.InsecureSkipVerify != true { - t.Fatalf(`tlsConfig.InsecureSkipVerify should be true`) - } -} - -func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: certificates, - } - - tlsConfig, err := NewTLSConfig(settings) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - - if tlsConfig == nil { - t.Fatalf(`tlsConfig should not be nil`) - } - - if tlsConfig.InsecureSkipVerify != false { - t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) - } - - if len(tlsConfig.Certificates) != 0 { - t.Fatalf(`tlsConfig.Certificates should be empty`) - } - - if tlsConfig.RootCAs == nil { - t.Fatalf(`tlsConfig.RootCAs should not be nil`) - } -} - -func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: certificates, - } - - _, err := NewTLSConfig(settings) - if err == nil { - t.Fatalf(`Expected an error`) - } - - if err.Error() != "failed to decode PEM block containing CA certificate" { - t.Fatalf(`Unexpected error message: %v`, err) - } -} - -func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.SelfSignedTLS, - Certificates: certificates, - } - - _, err := NewTLSConfig(settings) - if err == nil { - t.Fatalf(`Expected an error`) - } - - if err.Error() != "error parsing certificate: x509: inner and outer signature algorithm identifiers don't match" { - t.Fatalf(`Unexpected error message: %v`, err) - } -} - -func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: certificates, - } - - tlsConfig, err := NewTLSConfig(settings) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - - if tlsConfig == nil { - t.Fatalf(`tlsConfig should not be nil`) - } - - if tlsConfig.InsecureSkipVerify != false { - t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) - } - - if len(tlsConfig.Certificates) == 0 { - t.Fatalf(`tlsConfig.Certificates should not be empty`) - } - - if tlsConfig.RootCAs == nil { - t.Fatalf(`tlsConfig.RootCAs should not be nil`) - } -} - -func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: certificates, - } - - _, err := NewTLSConfig(settings) - if err == nil { - t.Fatalf(`Expected an error`) - } - - if err.Error() != "failed to decode PEM block containing CA certificate" { - t.Fatalf(`Unexpected error message: %v`, err) - } -} - -func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.SelfSignedMutualTLS, - Certificates: certificates, - } - - _, err := NewTLSConfig(settings) - if err == nil { - t.Fatalf(`Expected an error`) - } - - if err.Error() != "tls: failed to find any PEM data in certificate input" { - t.Fatalf(`Unexpected error message: %v`, err) - } -} - -func TestTlsFactory_CaTlsMode(t *testing.T) { - t.Parallel() - settings := configuration.Settings{ - TLSMode: configuration.CertificateAuthorityTLS, - } - - tlsConfig, err := NewTLSConfig(settings) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - - if tlsConfig == nil { - t.Fatalf(`tlsConfig should not be nil`) - } - - if tlsConfig.InsecureSkipVerify != false { - t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) - } - - if len(tlsConfig.Certificates) != 0 { - t.Fatalf(`tlsConfig.Certificates should be empty`) - } - - if tlsConfig.RootCAs != nil { - t.Fatalf(`tlsConfig.RootCAs should be nil`) - } -} - -func TestTlsFactory_CaMtlsMode(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.CertificateAuthorityMutualTLS, - Certificates: certificates, - } - - tlsConfig, err := NewTLSConfig(settings) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } - - if tlsConfig == nil { - t.Fatalf(`tlsConfig should not be nil`) - } - - if tlsConfig.InsecureSkipVerify != false { - t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) - } - - if len(tlsConfig.Certificates) == 0 { - t.Fatalf(`tlsConfig.Certificates should not be empty`) - } - - if tlsConfig.RootCAs != nil { - t.Fatalf(`tlsConfig.RootCAs should be nil`) - } -} - -func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { - t.Parallel() - certs := make(map[string]map[string]core.SecretBytes) - certs[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) - certs[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) - - certificates := certification.NewCertificates(nil, certs) - certificates.CaCertificateSecretKey = CaCertificateSecretKey - certificates.ClientCertificateSecretKey = ClientCertificateSecretKey - - settings := configuration.Settings{ - TLSMode: configuration.CertificateAuthorityMutualTLS, - Certificates: certificates, - } - - _, err := NewTLSConfig(settings) - if err == nil { - t.Fatalf(`Expected an error`) - } - - if err.Error() != "tls: failed to find any PEM data in certificate input" { - t.Fatalf(`Unexpected error message: %v`, err) - } -} - -// caCertificatePEM returns a PEM-encoded CA certificate. -// Note: The certificate is self-signed and generated explicitly for tests, -// it is not used anywhere else. -func caCertificatePEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIDTzCCAjcCFA4Zdj3E9TdjOP48eBRDGRLfkj7CMA0GCSqGSIb3DQEBCwUAMGQx -CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0 -dGxlMQ4wDAYDVQQKDAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFu -Y2VzMB4XDTIzMDkyOTE3MTY1MVoXDTIzMTAyOTE3MTY1MVowZDELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxDjAMBgNV -BAoMBU5HSU5YMR4wHAYDVQQLDBVDb21tdW5pdHkgJiBBbGxpYW5jZXMwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwlI4ZvJ/6hvqULFVL+1ZSRDTPQ48P -umehJhPz6xPhC9UkeTe2FZxm2Rsi1I5QXm/bTG2OcX775jgXzae9NQjctxwrz4Ks -LOWUvRkkfhQR67xk0Noux76/9GWGnB+Fapn54tlWql6uHQfOu1y7MCRkZ27zHbkk -lq4Oa2RmX8rIyECWgbTyL0kETBVJU8bYORQ5JjhRlz08inq3PggY8blrehIetrWN -dw+gzcqdvAI2uSCodHTHM/77KipnYmPiSiDjSDRlXdxTG8JnyIB78IoH/sw6RyBm -CvVa3ytvKziXAvbBoXq5On5WmMRF97p/MmBc53ExMuDZjA4fisnViS0PAgMBAAEw -DQYJKoZIhvcNAQELBQADggEBAJeoa2P59zopLjBInx/DnWn1N1CmFLb0ejKxG2jh -cOw15Sx40O0XrtrAto38iu4R/bkBeNCSUILlT+A3uYDila92Dayvls58WyIT3meD -G6+Sx/QDF69+4AXpVy9mQ+hxcofpFA32+GOMXwmk2OrAcdSkkGSBhZXgvTpQ64dl -xSiQ5EQW/K8LoBoEOXfjIZJNPORgKn5MI09AY7/47ycKDKTUU2yO8AtIHYKttw0x -kfIg7QOdo1F9IXVpGjJI7ynyrgsCEYxMoDyH42Dq84eKgrUFLEXemEz8hgdFgK41 -0eUYhAtzWHbRPBp+U/34CQoZ5ChNFp2YipvtXrzKE8KLkuM= ------END CERTIFICATE----- -` -} - -func invalidCertificatePEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIClzCCAX+gAwIBAgIJAIfPhC0RG6CwMA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV -BAMMDm9pbCBhdXRob3JpdHkwHhcNMjAwNDA3MTUwOTU1WhcNMjEwNDA2MTUwOTU1 -WjBMMSAwHgYDVQQLDBd5b3VuZy1jaGFsbGVuZ2UgdGVzdCBjb25zdW1lczEfMB0G -A1UECwwWc28wMS5jb3Jwb3JhdGlvbnNvY2lhbDEhMB8GA1UEAwwYc29tMS5jb3Jw -b3JhdGlvbnNvY2lhbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDGRX31uzy+yLUOz7wOJHHm2dzrDgUbC6RZDjURvZxyt2Zi5wYWsEB5r5YhN7L0 -y1R9f+MGwNITIz9nYZuU/PLFOvzF5qX7A8TbdgjZEqvXe2NZ9J2z3iWvYQLN8Py3 -nv/Y6wadgXEBRCNNuIg/bQ9XuOr9tfB6j4Ut1GLU0eIlV/L3Rf9Y6SgrAl+58ITj -Wrg3Js/Wz3J2JU4qBD8U4I3XvUyfnX2SAG8Llm4KBuYz7g63Iu05s6RnmG+Xhu2T -5f2DWZUeATWbAlUW/M4NLO1+5H0gOr0TGulETQ6uElMchT7s/H6Rv1CV+CNCCgEI -adRjWJq9yQ+KrE+urSMCXu8XAgMBAAGjUzBRMB0GA1UdDgQWBBRb40pKGU4lNvqB -1f5Mz3t0N/K3hzAfBgNVHSMEGDAWgBRb40pKGU4lNvqB1f5Mz3t0N/K3hzAPBgNV -HREECDAGhwQAAAAAAAAwCgYIKoZIzj0EAwIDSAAwRQIhAP3ST/mXyRXsU2ciRoE -gE6trllODFY+9FgT6UbF2TwzAiAAuaUxtbk6uXLqtD5NtXqOQf0Ckg8GQxc5V1G2 -9PqTXQ== ------END CERTIFICATE----- -` -} - -// Yoinked from https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/crypto/x509/x509_test.go, line 3385 -// This allows the `buildCaCertificatePool(...)` --> `x509.ParseCertificate(...)` call error branch to be covered. -func invalidCertificateDataPEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIBBzCBrqADAgECAgEAMAoGCCqGSM49BAMCMAAwIhgPMDAwMTAxMDEwMDAwMDBa -GA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOqV -EDuVXxwZgIU3+dOwv1SsMu0xuV48hf7xmK8n7sAMYgllB+96DnPqBeboJj4snYnx -0AcE0PDVQ1l4Z3YXsQWjFTATMBEGA1UdEQEB/wQHMAWCA2FzZDAKBggqhkjOPQQD -AwNIADBFAiBi1jz/T2HT5nAfrD7zsgR+68qh7Erc6Q4qlxYBOgKG4QIhAOtjIn+Q -tA+bq+55P3ntxTOVRq0nv1mwnkjwt9cQR9Fn ------END CERTIFICATE----- -` -} - -// clientCertificatePEM returns a PEM-encoded client certificate. -// Note: The certificate is self-signed and generated explicitly for tests, -// it is not used anywhere else. -func clientCertificatePEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL -BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM -CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu -dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT -MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK -DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk -H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI -ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto -oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 -Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA -TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 -MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx -g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM -Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy -MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ -KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt -eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK -ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE -Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH -R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k -Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= ------END CERTIFICATE----- -` -} - -// clientKeyPEM returns a PEM-encoded client key. -// Note: The key is self-signed and generated explicitly for tests, -// it is not used anywhere else. -func clientKeyPEM() string { - return ` ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub -NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 -/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g -bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek -sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 -R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde -wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 -CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 -rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg -NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e -/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB -ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md -MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ -Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT -FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe -OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 -X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE -1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex -JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig -iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp -r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy -SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB -OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ -sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC -mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 -z/3KkMx4uqJXZyvQrmkolSg= ------END PRIVATE KEY----- -` -} - -func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string]core.SecretBytes { - return map[string]core.SecretBytes{ - certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), - certification.CertificateKeyKey: core.SecretBytes([]byte(keyPEM)), - } -} - -func buildCaCertificateEntry(certificatePEM string) map[string]core.SecretBytes { - return map[string]core.SecretBytes{ - certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), - } -} diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go deleted file mode 100644 index 86b9320..0000000 --- a/internal/certification/certificates.go +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - * - * Establishes a Watcher for the Kubernetes Secrets that contain the various certificates - * and keys used to generate a tls.Config object; - * exposes the certificates and keys. - */ - -package certification - -import ( - "context" - "fmt" - "log/slog" - "sync" - - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/cache" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" -) - -const ( - // SecretsNamespace is the value used to filter the Secrets Resource in the Informer. - SecretsNamespace = "nlk" - - // CertificateKey is the key for the certificate in the Secret. - CertificateKey = "tls.crt" - - // CertificateKeyKey is the key for the certificate key in the Secret. - CertificateKeyKey = "tls.key" -) - -type Certificates struct { - mu sync.Mutex // guards Certificates - certificates map[string]map[string]core.SecretBytes - - // CaCertificateSecretKey is the name of the Secret that contains the Certificate Authority certificate. - CaCertificateSecretKey string - - // ClientCertificateSecretKey is the name of the Secret that contains the Client certificate. - ClientCertificateSecretKey string - - // informer is the SharedInformer used to watch for changes to the Secrets . - informer cache.SharedInformer - - // K8sClient is the Kubernetes client used to communicate with the Kubernetes API. - k8sClient kubernetes.Interface - - // eventHandlerRegistration is the object used to track the event handlers with the SharedInformer. - eventHandlerRegistration cache.ResourceEventHandlerRegistration -} - -// NewCertificates factory method that returns a new Certificates object. -func NewCertificates( - k8sClient kubernetes.Interface, certificates map[string]map[string]core.SecretBytes, -) *Certificates { - return &Certificates{ - k8sClient: k8sClient, - certificates: certificates, - } -} - -// GetCACertificate returns the Certificate Authority certificate. -func (c *Certificates) GetCACertificate() core.SecretBytes { - c.mu.Lock() - defer c.mu.Unlock() - - bytes := c.certificates[c.CaCertificateSecretKey][CertificateKey] - - return bytes -} - -// GetClientCertificate returns the Client certificate and key. -func (c *Certificates) GetClientCertificate() (core.SecretBytes, core.SecretBytes) { - c.mu.Lock() - defer c.mu.Unlock() - - keyBytes := c.certificates[c.ClientCertificateSecretKey][CertificateKeyKey] - certificateBytes := c.certificates[c.ClientCertificateSecretKey][CertificateKey] - - return keyBytes, certificateBytes -} - -// Initialize initializes the Certificates object. Sets up a SharedInformer for the Secrets Resource. -func (c *Certificates) Initialize() error { - slog.Info("Certificates::Initialize") - - var err error - - c.mu.Lock() - c.certificates = make(map[string]map[string]core.SecretBytes) - c.mu.Unlock() - - informer := c.buildInformer() - - c.informer = informer - - err = c.initializeEventHandlers() - if err != nil { - return fmt.Errorf(`error occurred initializing event handlers: %w`, err) - } - - return nil -} - -// Run starts the SharedInformer. -func (c *Certificates) Run(ctx context.Context) error { - slog.Info("Certificates::Run") - - if c.informer == nil { - return fmt.Errorf(`initialize must be called before Run`) - } - - c.informer.Run(ctx.Done()) - - <-ctx.Done() - - return nil -} - -func (c *Certificates) buildInformer() cache.SharedInformer { - slog.Debug("Certificates::buildInformer") - - options := informers.WithNamespace(SecretsNamespace) - factory := informers.NewSharedInformerFactoryWithOptions(c.k8sClient, 0, options) - informer := factory.Core().V1().Secrets().Informer() - - return informer -} - -func (c *Certificates) initializeEventHandlers() error { - slog.Debug("Certificates::initializeEventHandlers") - - var err error - - handlers := cache.ResourceEventHandlerFuncs{ - AddFunc: c.handleAddEvent, - DeleteFunc: c.handleDeleteEvent, - UpdateFunc: c.handleUpdateEvent, - } - - c.eventHandlerRegistration, err = c.informer.AddEventHandler(handlers) - if err != nil { - return fmt.Errorf(`error occurred registering event handlers: %w`, err) - } - - return nil -} - -func (c *Certificates) handleAddEvent(obj interface{}) { - slog.Debug("Certificates::handleAddEvent") - - secret, ok := obj.(*corev1.Secret) - if !ok { - slog.Error("Certificates::handleAddEvent: unable to cast object to Secret") - return - } - - c.mu.Lock() - defer c.mu.Unlock() - - c.certificates[secret.Name] = map[string]core.SecretBytes{} - - // Input from the secret comes in the form - // tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVCVEN... - // tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0l... - // Where the keys are `tls.crt` and `tls.key` and the values are []byte - for k, v := range secret.Data { - c.certificates[secret.Name][k] = core.SecretBytes(v) - } - - slog.Debug("Certificates::handleAddEvent", slog.Int("certCount", len(c.certificates))) -} - -func (c *Certificates) handleDeleteEvent(obj interface{}) { - slog.Debug("Certificates::handleDeleteEvent") - - secret, ok := obj.(*corev1.Secret) - if !ok { - slog.Error("Certificates::handleDeleteEvent: unable to cast object to Secret") - return - } - - c.mu.Lock() - defer c.mu.Unlock() - - if c.certificates[secret.Name] != nil { - delete(c.certificates, secret.Name) - } - - slog.Debug("Certificates::handleDeleteEvent", slog.Int("certCount", len(c.certificates))) -} - -func (c *Certificates) handleUpdateEvent(_ interface{}, newValue interface{}) { - slog.Debug("Certificates::handleUpdateEvent") - - secret, ok := newValue.(*corev1.Secret) - if !ok { - slog.Error("Certificates::handleUpdateEvent: unable to cast object to Secret") - return - } - - c.mu.Lock() - defer c.mu.Unlock() - - for k, v := range secret.Data { - c.certificates[secret.Name][k] = v - } - - slog.Debug("Certificates::handleUpdateEvent", slog.Int("certCount", len(c.certificates))) -} diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go deleted file mode 100644 index 7d2363b..0000000 --- a/internal/certification/certificates_test.go +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package certification - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/tools/cache" -) - -const ( - CaCertificateSecretKey = "nlk-tls-ca-secret" -) - -func TestNewCertificate(t *testing.T) { - t.Parallel() - - certificates := NewCertificates(nil, nil) - - if certificates == nil { - t.Fatalf(`certificates should not be nil`) - } -} - -func TestCertificates_Initialize(t *testing.T) { - t.Parallel() - certificates := NewCertificates(nil, nil) - - err := certificates.Initialize() - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } -} - -func TestCertificates_RunWithoutInitialize(t *testing.T) { - t.Parallel() - certificates := NewCertificates(nil, nil) - - err := certificates.Run(context.Background()) - if err == nil { - t.Fatalf(`Expected error`) - } - - if err.Error() != `initialize must be called before Run` { - t.Fatalf(`Unexpected error: %v`, err) - } -} - -func TestCertificates_EmptyCertificates(t *testing.T) { - t.Parallel() - certificates := NewCertificates(nil, nil) - - err := certificates.Initialize() - if err != nil { - t.Fatalf(`error Initializing Certificates: %v`, err) - } - - caBytes := certificates.GetCACertificate() - if caBytes != nil { - t.Fatalf(`Expected nil CA certificate`) - } - - clientKey, clientCert := certificates.GetClientCertificate() - if clientKey != nil { - t.Fatalf(`Expected nil client key`) - } - if clientCert != nil { - t.Fatalf(`Expected nil client certificate`) - } -} - -func TestCertificates_ExerciseHandlers(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - k8sClient := fake.NewSimpleClientset() - - certificates := NewCertificates(k8sClient, nil) - - _ = certificates.Initialize() - - certificates.CaCertificateSecretKey = CaCertificateSecretKey - - //nolint:govet,staticcheck - go func() { - err := certificates.Run(context.Background()) - assert.NoError(t, err, "expected no error running certificates") - }() - - cache.WaitForCacheSync(ctx.Done(), certificates.informer.HasSynced) - - secret := buildSecret() - - /* -- Test Create -- */ - - created, err := k8sClient.CoreV1().Secrets(SecretsNamespace).Create(ctx, secret, metav1.CreateOptions{}) - if err != nil { - t.Fatalf(`error creating the Secret: %v`, err) - } - - if created.Name != secret.Name { - t.Fatalf(`Expected name %v, got %v`, secret.Name, created.Name) - } - - time.Sleep(2 * time.Second) - - caBytes := certificates.GetCACertificate() - if caBytes == nil { - t.Fatalf(`Expected non-nil CA certificate`) - } - - /* -- Test Update -- */ - - secret.Labels = map[string]string{"updated": "true"} - _, err = k8sClient.CoreV1().Secrets(SecretsNamespace).Update(ctx, secret, metav1.UpdateOptions{}) - if err != nil { - t.Fatalf(`error updating the Secret: %v`, err) - } - - time.Sleep(2 * time.Second) - - caBytes = certificates.GetCACertificate() - if caBytes == nil { - t.Fatalf(`Expected non-nil CA certificate`) - } - - /* -- Test Delete -- */ - - err = k8sClient.CoreV1().Secrets(SecretsNamespace).Delete(ctx, secret.Name, metav1.DeleteOptions{}) - if err != nil { - t.Fatalf(`error deleting the Secret: %v`, err) - } - - time.Sleep(2 * time.Second) - - caBytes = certificates.GetCACertificate() - if caBytes != nil { - t.Fatalf(`Expected nil CA certificate, got: %v`, caBytes) - } -} - -func buildSecret() *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: CaCertificateSecretKey, - Namespace: SecretsNamespace, - }, - Data: map[string][]byte{ - CertificateKey: []byte(certificatePEM()), - CertificateKeyKey: []byte(keyPEM()), - }, - Type: corev1.SecretTypeTLS, - } -} - -// certificatePEM returns a PEM-encoded client certificate. -// Note: The certificate is self-signed and generated explicitly for tests, -// it is not used anywhere else. -func certificatePEM() string { - return ` ------BEGIN CERTIFICATE----- -MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL -BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM -CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu -dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT -MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK -DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk -H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI -ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto -oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 -Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA -TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 -MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx -g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM -Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy -MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ -KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt -eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK -ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE -Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH -R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k -Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= ------END CERTIFICATE----- -` -} - -// keyPEM returns a PEM-encoded client key. -// Note: The key is self-signed and generated explicitly for tests, -// it is not used anywhere else. -func keyPEM() string { - return ` ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub -NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 -/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g -bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek -sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 -R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde -wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 -CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 -rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg -NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e -/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB -ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md -MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ -Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT -FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe -OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 -X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE -1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex -JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig -iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp -r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy -SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB -OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ -sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC -mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 -z/3KkMx4uqJXZyvQrmkolSg= ------END PRIVATE KEY----- -` -} diff --git a/internal/certification/doc.go b/internal/certification/doc.go deleted file mode 100644 index 3388ea0..0000000 --- a/internal/certification/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -/* -Package certification includes functionality to access the Secrets containing the TLS Certificates. -*/ - -package certification diff --git a/internal/communication/factory.go b/internal/communication/factory.go index cf1bfcb..2084d5c 100644 --- a/internal/communication/factory.go +++ b/internal/communication/factory.go @@ -6,24 +6,19 @@ package communication import ( - "crypto/tls" "fmt" - "log/slog" netHttp "net/http" "time" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/nginxinc/kubernetes-nginx-ingress/pkg/buildinfo" ) -// NewHTTPClient is a factory method to create a new Http Client with a default configuration. -// RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, -// the Headers are configured for JSON. -func NewHTTPClient(settings configuration.Settings) (*netHttp.Client, error) { - headers := NewHeaders(settings.APIKey) - tlsConfig := NewTLSConfig(settings) - transport := NewTransport(tlsConfig) +// NewHTTPClient is a factory method to create a new Http Client configured for +// working with NGINXaaS or the N+ api. If skipVerify is set to true, the http +// transport will skip TLS certificate verification. +func NewHTTPClient(apiKey string, skipVerify bool) (*netHttp.Client, error) { + headers := NewHeaders(apiKey) + transport := NewTransport(skipVerify) roundTripper := NewRoundTripper(headers, transport) return &netHttp.Client{ @@ -49,22 +44,10 @@ func NewHeaders(apiKey string) []string { return headers } -// NewTLSConfig is a factory method to create a new basic Tls Config. -// More attention should be given to the use of `InsecureSkipVerify: true`, as it is not recommended for production use. -func NewTLSConfig(settings configuration.Settings) *tls.Config { - tlsConfig, err := authentication.NewTLSConfig(settings) - if err != nil { - slog.Warn("Failed to create TLS config", "error", err) - return &tls.Config{InsecureSkipVerify: true} //nolint:gosec - } - - return tlsConfig -} - // NewTransport is a factory method to create a new basic Http Transport. -func NewTransport(config *tls.Config) *netHttp.Transport { +func NewTransport(skipVerify bool) *netHttp.Transport { transport := netHttp.DefaultTransport.(*netHttp.Transport).Clone() - transport.TLSClientConfig = config + transport.TLSClientConfig.InsecureSkipVerify = skipVerify return transport } diff --git a/internal/communication/factory_test.go b/internal/communication/factory_test.go index 7562484..8c637a8 100644 --- a/internal/communication/factory_test.go +++ b/internal/communication/factory_test.go @@ -7,12 +7,14 @@ package communication import ( "testing" + + "github.com/stretchr/testify/require" ) func TestNewHTTPClient(t *testing.T) { t.Parallel() - client, err := NewHTTPClient(defaultSettings()) + client, err := NewHTTPClient("fakeKey", true) if err != nil { t.Fatalf(`Unexpected error: %v`, err) } @@ -80,8 +82,7 @@ func TestNewHeadersWithNoAPIKey(t *testing.T) { func TestNewTransport(t *testing.T) { t.Parallel() - config := NewTLSConfig(defaultSettings()) - transport := NewTransport(config) + transport := NewTransport(false) if transport == nil { t.Fatalf(`transport should not be nil`) @@ -91,11 +92,5 @@ func TestNewTransport(t *testing.T) { t.Fatalf(`transport.TLSClientConfig should not be nil`) } - if transport.TLSClientConfig != config { - t.Fatalf(`transport.TLSClientConfig should be the same as config`) - } - - if !transport.TLSClientConfig.InsecureSkipVerify { - t.Fatalf(`transport.TLSClientConfig.InsecureSkipVerify should be true`) - } + require.False(t, transport.TLSClientConfig.InsecureSkipVerify) } diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index 55dea88..d00af17 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -9,15 +9,13 @@ import ( "bytes" netHttp "net/http" "testing" - - "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" ) func TestNewRoundTripper(t *testing.T) { t.Parallel() headers := NewHeaders("fakeKey") - transport := NewTransport(NewTLSConfig(defaultSettings())) + transport := NewTransport(true) roundTripper := NewRoundTripper(headers, transport) if roundTripper == nil { @@ -57,7 +55,7 @@ func TestRoundTripperRoundTrip(t *testing.T) { t.Parallel() headers := NewHeaders("fakeKey") - transport := NewTransport(NewTLSConfig(defaultSettings())) + transport := NewTransport(true) roundTripper := NewRoundTripper(headers, transport) request, err := NewRequest("GET", "http://example.com", nil) @@ -92,9 +90,3 @@ func NewRequest(method string, url string, body []byte) (*netHttp.Request, error return request, nil } - -func defaultSettings() configuration.Settings { - return configuration.Settings{ - TLSMode: configuration.NoTLS, - } -} diff --git a/internal/configuration/configuration_test.go b/internal/configuration/configuration_test.go index fc41974..45764d6 100644 --- a/internal/configuration/configuration_test.go +++ b/internal/configuration/configuration_test.go @@ -4,13 +4,12 @@ import ( "testing" "time" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" "github.com/stretchr/testify/require" ) -func TestConfiguration(t *testing.T) { +func TestConfiguration_Read(t *testing.T) { t.Parallel() tests := map[string]struct { @@ -22,11 +21,7 @@ func TestConfiguration(t *testing.T) { expectedSettings: configuration.Settings{ LogLevel: "warn", NginxPlusHosts: []string{"https://10.0.0.1:9000/api"}, - TLSMode: configuration.NoTLS, - Certificates: &certification.Certificates{ - CaCertificateSecretKey: "fakeCAKey", - ClientCertificateSecretKey: "fakeCertKey", - }, + SkipVerifyTLS: false, Handler: configuration.HandlerSettings{ RetryCount: 5, Threads: 1, @@ -58,11 +53,7 @@ func TestConfiguration(t *testing.T) { expectedSettings: configuration.Settings{ LogLevel: "warn", NginxPlusHosts: []string{"https://10.0.0.1:9000/api", "https://10.0.0.2:9000/api"}, - TLSMode: configuration.NoTLS, - Certificates: &certification.Certificates{ - CaCertificateSecretKey: "fakeCAKey", - ClientCertificateSecretKey: "fakeCertKey", - }, + SkipVerifyTLS: true, Handler: configuration.HandlerSettings{ RetryCount: 5, Threads: 1, @@ -100,3 +91,48 @@ func TestConfiguration(t *testing.T) { }) } } + +func TestConfiguration_TLS(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + tlsMode string + expectedSkipVerifyTLS bool + expectedErr bool + }{ + "no input": { + tlsMode: "", + expectedSkipVerifyTLS: false, + }, + "no tls": { + tlsMode: "no-tls", + expectedSkipVerifyTLS: true, + }, + "skip verify tls": { + tlsMode: "skip-verify-tls", + expectedSkipVerifyTLS: true, + }, + "ca tls": { + tlsMode: "ca-tls", + expectedSkipVerifyTLS: false, + }, + "unexpected input": { + tlsMode: "unexpected-tls-mode", + expectedErr: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + skipVerifyTLS, err := configuration.ValidateTLSMode(tc.tlsMode) + if tc.expectedErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedSkipVerifyTLS, skipVerifyTLS) + }) + } +} diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index ebeacc4..75cec2e 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -11,8 +11,6 @@ import ( "log/slog" "time" - "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" - "github.com/spf13/viper" ) @@ -110,16 +108,12 @@ type Settings struct { // NginxPlusHosts is a list of Nginx Plus hosts that will be used to update the Border Servers. NginxPlusHosts []string - // TlsMode is the value used to determine which of the five TLS modes will be used to communicate - // with the Border Servers (see: ../../docs/tls/README.md). - TLSMode TLSMode + // SkipVerifyTLS determines whether the http client will skip TLS verification or not. + SkipVerifyTLS bool // APIKey is the api key used to authenticate with the dataplane API. APIKey string - // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. - Certificates *certification.Certificates - // Handler contains the configuration values needed by the Handler. Handler HandlerSettings @@ -144,11 +138,13 @@ func Read(configName, configPath string) (s Settings, err error) { return s, err } - tlsMode := NoTLS - if t, err := validateTLSMode(v.GetString("tls-mode")); err != nil { + skipVerifyTLS, err := ValidateTLSMode(v.GetString("tls-mode")) + if err != nil { slog.Error("could not validate tls mode", "error", err) - } else { - tlsMode = t + } + + if skipVerifyTLS { + slog.Warn("skipping TLS verification for NGINX hosts") } serviceAnnotation := DefaultServiceAnnotation @@ -159,12 +155,8 @@ func Read(configName, configPath string) (s Settings, err error) { return Settings{ LogLevel: v.GetString("log-level"), NginxPlusHosts: v.GetStringSlice("nginx-hosts"), - TLSMode: tlsMode, + SkipVerifyTLS: skipVerifyTLS, APIKey: base64.StdEncoding.EncodeToString([]byte(v.GetString("NGINXAAS_DATAPLANE_API_KEY"))), - Certificates: &certification.Certificates{ - CaCertificateSecretKey: v.GetString("ca-certificate"), - ClientCertificateSecretKey: v.GetString("client-certificate"), - }, Handler: HandlerSettings{ RetryCount: 5, Threads: 1, @@ -192,10 +184,15 @@ func Read(configName, configPath string) (s Settings, err error) { }, nil } -func validateTLSMode(tlsConfigMode string) (TLSMode, error) { - if tlsMode, tlsModeFound := TLSModeMap[tlsConfigMode]; tlsModeFound { - return tlsMode, nil +func ValidateTLSMode(tlsConfigMode string) (skipVerify bool, err error) { + if tlsConfigMode == "" { + return false, nil + } + + var tlsModeFound bool + if skipVerify, tlsModeFound = tlsModeMap[tlsConfigMode]; tlsModeFound { + return skipVerify, nil } - return NoTLS, fmt.Errorf(`invalid tls-mode value: %s`, tlsConfigMode) + return false, fmt.Errorf(`invalid tls-mode value: %s`, tlsConfigMode) } diff --git a/internal/configuration/testdata/one-nginx-host.yaml b/internal/configuration/testdata/one-nginx-host.yaml index f05d81e..45e55ef 100644 --- a/internal/configuration/testdata/one-nginx-host.yaml +++ b/internal/configuration/testdata/one-nginx-host.yaml @@ -2,7 +2,6 @@ ca-certificate: "fakeCAKey" client-certificate: "fakeCertKey" log-level: "warn" nginx-hosts: "https://10.0.0.1:9000/api" -tls-mode: "no-tls" service-annotation-match: "fakeServiceMatch" creationTimestamp: "2024-09-04T17:59:20Z" name: "nlk-config" diff --git a/internal/configuration/tlsmodes.go b/internal/configuration/tlsmodes.go index 2f7271f..e329047 100644 --- a/internal/configuration/tlsmodes.go +++ b/internal/configuration/tlsmodes.go @@ -6,41 +6,19 @@ package configuration const ( - NoTLS TLSMode = iota - CertificateAuthorityTLS - CertificateAuthorityMutualTLS - SelfSignedTLS - SelfSignedMutualTLS + // NoTLS is deprecated as misleading. It is the same as SkipVerifyTLS. + NoTLS = "no-tls" + // SkipVerifyTLS causes the http client to skip verification of the NGINX + // host's certificate chain and host name. + SkipVerifyTLS = "skip-verify-tls" + // CertificateAuthorityTLS is deprecated as misleading. This is the same as + // the default behavior which is to verify the NGINX hosts's certificate + // chain and host name, if https is used. + CertificateAuthorityTLS = "ca-tls" ) -const ( - NoTLSString = "no-tls" - CertificateAuthorityTLSString = "ca-tls" - CertificateAuthorityMutualTLSString = "ca-mtls" - SelfSignedTLSString = "ss-tls" - SelfSignedMutualTLSString = "ss-mtls" -) - -type TLSMode int - -var TLSModeMap = map[string]TLSMode{ - NoTLSString: NoTLS, - CertificateAuthorityTLSString: CertificateAuthorityTLS, - CertificateAuthorityMutualTLSString: CertificateAuthorityMutualTLS, - SelfSignedTLSString: SelfSignedTLS, - SelfSignedMutualTLSString: SelfSignedMutualTLS, -} - -func (t TLSMode) String() string { - modes := []string{ - NoTLSString, - CertificateAuthorityTLSString, - CertificateAuthorityMutualTLSString, - SelfSignedTLSString, - SelfSignedMutualTLSString, - } - if t < NoTLS || t > SelfSignedMutualTLS { - return "" - } - return modes[t] +var tlsModeMap = map[string]bool{ + NoTLS: true, + SkipVerifyTLS: true, + CertificateAuthorityTLS: false, } diff --git a/internal/configuration/tlsmodes_test.go b/internal/configuration/tlsmodes_test.go deleted file mode 100644 index d849cd9..0000000 --- a/internal/configuration/tlsmodes_test.go +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package configuration - -import ( - "testing" -) - -func Test_String(t *testing.T) { - t.Parallel() - mode := NoTLS.String() - if mode != "no-tls" { - t.Errorf("Expected TLSModeNoTLS to be 'no-tls', got '%s'", mode) - } - - mode = CertificateAuthorityTLS.String() - if mode != "ca-tls" { - t.Errorf("Expected TLSModeCaTLS to be 'ca-tls', got '%s'", mode) - } - - mode = CertificateAuthorityMutualTLS.String() - if mode != "ca-mtls" { - t.Errorf("Expected TLSModeCaMTLS to be 'ca-mtls', got '%s'", mode) - } - - mode = SelfSignedTLS.String() - if mode != "ss-tls" { - t.Errorf("Expected TLSModeSsTLS to be 'ss-tls', got '%s'", mode) - } - - mode = SelfSignedMutualTLS.String() - if mode != "ss-mtls" { - t.Errorf("Expected TLSModeSsMTLS to be 'ss-mtls', got '%s',", mode) - } - - mode = TLSMode(5).String() - if mode != "" { - t.Errorf("Expected TLSMode(5) to be '', got '%s'", mode) - } -} - -func Test_TLSModeMap(t *testing.T) { - t.Parallel() - mode := TLSModeMap["no-tls"] - if mode != NoTLS { - t.Errorf("Expected TLSModeMap['no-tls'] to be TLSModeNoTLS, got '%d'", mode) - } - - mode = TLSModeMap["ca-tls"] - if mode != CertificateAuthorityTLS { - t.Errorf("Expected TLSModeMap['ca-tls'] to be TLSModeCaTLS, got '%d'", mode) - } - - mode = TLSModeMap["ca-mtls"] - if mode != CertificateAuthorityMutualTLS { - t.Errorf("Expected TLSModeMap['ca-mtls'] to be TLSModeCaMTLS, got '%d'", mode) - } - - mode = TLSModeMap["ss-tls"] - if mode != SelfSignedTLS { - t.Errorf("Expected TLSModeMap['ss-tls'] to be TLSModeSsTLS, got '%d'", mode) - } - - mode = TLSModeMap["ss-mtls"] - if mode != SelfSignedMutualTLS { - t.Errorf("Expected TLSModeMap['ss-mtls'] to be TLSModeSsMTLS, got '%d'", mode) - } - - mode = TLSModeMap["invalid"] - if mode != TLSMode(0) { - t.Errorf("Expected TLSModeMap['invalid'] to be TLSMode(0), got '%d'", mode) - } -} diff --git a/internal/core/secret_bytes.go b/internal/core/secret_bytes.go deleted file mode 100644 index 0bbc3bf..0000000 --- a/internal/core/secret_bytes.go +++ /dev/null @@ -1,21 +0,0 @@ -package core - -import ( - "encoding/json" -) - -// Wraps byte slices which potentially could contain -// sensitive data that should not be output to the logs. -// This will output [REDACTED] if attempts are made -// to print this type in logs, serialize to JSON, or -// otherwise convert it to a string. -// Usage: core.SecretBytes(myByteSlice) -type SecretBytes []byte - -func (sb SecretBytes) String() string { - return "[REDACTED]" -} - -func (sb SecretBytes) MarshalJSON() ([]byte, error) { - return json.Marshal("[REDACTED]") -} diff --git a/internal/core/secret_bytes_test.go b/internal/core/secret_bytes_test.go deleted file mode 100644 index 5e6bc3f..0000000 --- a/internal/core/secret_bytes_test.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2023 F5 Inc. All rights reserved. - * Use of this source code is governed by the Apache License that can be found in the LICENSE file. - */ - -package core - -import ( - "encoding/json" - "fmt" - "testing" -) - -func TestSecretBytesToString(t *testing.T) { - t.Parallel() - sensitive := SecretBytes([]byte("If you can see this we have a problem")) - - expected := "foo [REDACTED] bar" - result := fmt.Sprintf("foo %v bar", sensitive) - if result != expected { - t.Errorf("Expected %s, got %s", expected, result) - } -} - -func TestSecretBytesToJSON(t *testing.T) { - t.Parallel() - sensitive, _ := json.Marshal(SecretBytes([]byte("If you can see this we have a problem"))) - expected := `foo "[REDACTED]" bar` - result := fmt.Sprintf("foo %v bar", string(sensitive)) - if result != expected { - t.Errorf("Expected %s, got %s", expected, result) - } -} diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 80faaa3..bbae5e1 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -126,8 +126,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica slog.Debug(`Synchronizer::buildBorderClient`) var err error - - httpClient, err := communication.NewHTTPClient(s.settings) + httpClient, err := communication.NewHTTPClient(s.settings.APIKey, s.settings.SkipVerifyTLS) if err != nil { return nil, fmt.Errorf(`error creating HTTP client: %v`, err) } From 06d6bd1b398a90ca751d219978395aed78d8b0e6 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 13 Mar 2025 16:30:52 -0600 Subject: [PATCH 132/136] NLB-6295 Bumped version to 1.2.0 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 65087b4..26aaba0 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.1.4 +1.2.0 From a8edd189af3a1afdb499ced9129713f02377ca03 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 1 Jul 2025 11:51:34 -0600 Subject: [PATCH 133/136] Updated go version to 1.24.4 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c09a4b4..b49ed5c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ module github.com/nginxinc/kubernetes-nginx-ingress -go 1.24.3 +go 1.24.4 require ( github.com/nginx/nginx-plus-go-client/v2 v2.3.0 From 44894101992010b509091ebdbf57eaf965d52fed Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 2 Jul 2025 14:35:47 -0600 Subject: [PATCH 134/136] NLB-6754 When deleting upstream servers handle upstream not found error Now that the plus go client allows users to check the http status code of the error, handle the upstream not found case by doing nothing. --- go.mod | 4 ++-- go.sum | 8 ++++---- internal/synchronization/synchronizer.go | 15 ++++++++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b49ed5c..6a78fe2 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ module github.com/nginxinc/kubernetes-nginx-ingress go 1.24.4 require ( - github.com/nginx/nginx-plus-go-client/v2 v2.3.0 + github.com/nginx/nginx-plus-go-client/v2 v2.4.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - golang.org/x/sync v0.12.0 + golang.org/x/sync v0.13.0 k8s.io/api v0.33.1 k8s.io/apimachinery v0.33.1 k8s.io/client-go v0.33.1 diff --git a/go.sum b/go.sum index 80d0e9d..8cf5ed8 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nginx/nginx-plus-go-client/v2 v2.3.0 h1:ciKh1lwadNzUaOGjLcKWu/BGigASxU6p7v/6US71fhA= -github.com/nginx/nginx-plus-go-client/v2 v2.3.0/go.mod h1:U7G5pqucUS1V4Uecs1xCsJ9knSsfwqhwu8ZEjoCYnmk= +github.com/nginx/nginx-plus-go-client/v2 v2.4.0 h1:4c7V57CLCZUOxQCUcS9G8a5MClzdmxByBm+f4zKMzAY= +github.com/nginx/nginx-plus-go-client/v2 v2.4.0/go.mod h1:P+dIP2oKYzFoyf/zlLWQa8Sf+fHb+CclOKzxAjxpvug= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= @@ -135,8 +135,8 @@ golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index bbae5e1..8f0fff6 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -10,7 +10,7 @@ import ( "errors" "fmt" "log/slog" - "strings" + "net/http" "time" nginxClient "github.com/nginx/nginx-plus-go-client/v2/client" @@ -36,6 +36,13 @@ type Interface interface { ShutDown() } +// StatusError is a wrapper for errors from the go plus client that contain http +// status codes. +type StatusError interface { + Status() int + Code() string +} + type Translator interface { Translate(*core.Event) (core.ServerUpdateEvents, error) } @@ -264,11 +271,13 @@ func (s *Synchronizer) handleDeletedEvent(ctx context.Context, serverUpdateEvent err = borderClient.Update(ctx, serverUpdateEvent) + var se StatusError switch { case err == nil: return nil - // checking the string is not ideal, but the plus client gives us no option - case strings.Contains(err.Error(), "status=404"): + case errors.As(err, &se) && se.Status() == http.StatusNotFound: + // if the user has already removed the upstream from their NGINX + // configuration there is nothing left to do return nil default: return fmt.Errorf(`error occurred deleting the %s upstream servers: %w`, serverUpdateEvent.ClientType, err) From 38bf9ab4fa68983f6f19ecaf89e5a603d5c1cba7 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Wed, 2 Jul 2025 14:57:04 -0600 Subject: [PATCH 135/136] NLB-6754 Bumped version to 1.2.1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 26aaba0..6085e94 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.2.0 +1.2.1 From a9c0f148d5d324d8b6dda4c0ed240caca9b07237 Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Tue, 8 Jul 2025 16:27:20 -0600 Subject: [PATCH 136/136] Removed context as a field within the nginx stream border client This is a go anti-pattern --- internal/application/nginx_stream_border_client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/application/nginx_stream_border_client.go b/internal/application/nginx_stream_border_client.go index a65be86..238a22b 100644 --- a/internal/application/nginx_stream_border_client.go +++ b/internal/application/nginx_stream_border_client.go @@ -18,7 +18,6 @@ import ( type NginxStreamBorderClient struct { BorderClient nginxClient NginxClientInterface - ctx context.Context } // NewNginxStreamBorderClient is the Factory function for creating an NginxStreamBorderClient. @@ -30,7 +29,6 @@ func NewNginxStreamBorderClient(client interface{}) (Interface, error) { return &NginxStreamBorderClient{ nginxClient: ngxClient, - ctx: context.Background(), }, nil }