[
  {
    "path": ".bazelignore",
    "content": "src/.gopath\n"
  },
  {
    "path": ".bazelrc",
    "content": "# Enable Bzlmod for every Bazel command\ncommon --enable_bzlmod\n\n# Work around go issue with LLVM 15+: https://github.com/bazelbuild/rules_go/issues/3691#issuecomment-2263999685\nbuild --@io_bazel_rules_go//go/config:linkmode=pie\n\n# Enforce stricter environment rules, which eliminates some non-hermetic\n# behavior and therefore improves both the remote_cache cache hit rate and the\n# correctness and repeatability of the build.\nbuild --incompatible_strict_action_env=true\n\n# Make sure that no regressions are introduced until the flag is flipped\n# See: https://github.com/bazelbuild/bazel/issues/8195\nbuild --incompatible_disallow_empty_glob\n\n# Use the new paths.\n# https://github.com/bazelbuild/bazel/issues/23127\ncommon --incompatible_use_plus_in_repo_names\n\n# Always use the pre-configured toolchain.\nbuild --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1\nbuild --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1\n\n# Set a higher timeout value, just in case.\nbuild --remote_timeout=3600\n\n# Platform flags\n# The toolchain container used for execution is defined in the target indicated\n# by \"extra_execution_platforms\", \"host_platform\" and \"platforms\".\n# More about platforms: https://docs.bazel.build/versions/master/platforms.html\nbuild:linux_x86_64 --extra_execution_platforms=//bazel:linux_x86_64\nbuild:linux_x86_64 --host_platform=//bazel:linux_x86_64\nbuild:linux_x86_64 --platforms=//bazel:linux_x86_64\n\nbuild:remote       --remote_executor=grpcs://remotebuildexecution.googleapis.com\nbuild:remote_cache --remote_cache=grpcs://remotebuildexecution.googleapis.com\n\n# Enable authentication. This will pick up application default credentials by\n# default. You can use --google_credentials=some_file.json to use a service\n# account credential instead.\nbuild:remote       --google_default_credentials=true\nbuild:remote_cache --google_default_credentials=true\n\n# RBE builds only support linux_x86_64.\nbuild:remote       --config=linux_x86_64\nbuild:remote_cache --config=linux_x86_64\n\n# Don't run integration tests and tests that need docker by default\ntest --test_tag_filters=\"-external,-requires-docker\"\n"
  },
  {
    "path": ".bazelversion",
    "content": "8.4.2\n"
  },
  {
    "path": ".dockerignore",
    "content": "*\n"
  },
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.go]\nindent_style = tab\nindent_size = 8\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/ci/.bazelrc",
    "content": "# Bazel config for CI/CD builds.\n\n# Default to keep going\nbuild --keep_going\n\n# Use rbe remote execution and caching on robco-integration-test.\nbuild --config=remote\nbuild --remote_instance_name=projects/robco-integration-test/instances/default_instance\nbuild --google_default_credentials=true\n# Slightly higher than the numer of available remote workers (10 in default_instance).\n# This has not been tuned a lot.\nbuild --jobs=12\n# No neeed to download every intermediate output to the local runner.\nbuild --remote_download_toplevel\n\n# Use Result Store to store Build and Test logs .\nbuild --bes_backend=buildeventservice.googleapis.com\nbuild --bes_results_url=https://source.cloud.google.com/results/invocations\nbuild --bes_timeout=600s\nbuild --bes_instance_name=robco-integration-test\n# Try to mitigate DEADLINE_EXCEEDED errors (b/346715839).\n# Remove experimental_ prefix when updating Bazel.\nbuild --experimental_build_event_upload_max_retries=8\n"
  },
  {
    "path": ".github/ci/Dockerfile.integration-test-image",
    "content": "# Image used for integration_test.sh on Cloud Build.\n# Allows access to GKE and to run Bazel commands.\nFROM gcr.io/cloud-builders/kubectl\n\n# Install Bazelisk\nRUN \\\n    VERSION=\"v1.21.0\" && \\\n    curl -L https://github.com/bazelbuild/bazelisk/releases/download/${VERSION}/bazelisk-linux-amd64 --output /usr/bin/bazelisk && \\\n    chmod +x /usr/bin/bazelisk && \\\n    ln -s /usr/bin/bazelisk /usr/bin/bazel\n\nRUN mkdir -p /builder /output /workspace && chmod -R 777 /output\n\n# rules_python is not happy if bazel runs as root so create a new user\n# https://github.com/bazelbuild/rules_python/pull/713\n# https://github.com/GoogleCloudPlatform/cloud-builders/issues/641\nRUN adduser builder --disabled-password\n\n# Allow running sudo without password\n# Add libtinfo5, which is required locally until we can upgrade to LLVM 19\nRUN apt-get update && apt-get install -y sudo libtinfo5 && apt-get clean && rm -rf /var/lib/apt/lists/* && \\\n    usermod -aG sudo builder && \\\n    echo \"builder ALL=(ALL) NOPASSWD:ALL\" > \"/etc/sudoers.d/builder\" && chmod 440 \"/etc/sudoers.d/builder\"\n\n# For some reason //src/go/tests:go_default_test is expecting\n# the kubeconfig in /home/builder/.kube/config, i.e. it does not use $HOME\n# (which is /builder/home). alexanderfaxa@ could not figure out why so just\n# copy the config there.\nRUN mkdir -p /home/builder/.kube && \\\n    ln -s /builder/home/.kube/config /home/builder/.kube/config\n\nUSER builder\n"
  },
  {
    "path": ".github/ci/common.sh",
    "content": "#!/bin/bash\n\n# Format for the xtrace lines\nexport 'PS4=+$(date --rfc-3339=seconds):${BASH_SOURCE}:${LINENO}: '\nset -o errexit   # exit immediately, if a pipeline command fails\nset -o pipefail  # returns the last command to exit with a non-zero status\nset -o xtrace    # print command traces before executing command\n\nRUNFILES_ROOT=\"_main\"\n\n# Wraps the common Bazel flags for CI for brevity.\nfunction bazel_ci {\n  bazelisk --bazelrc=\"${DIR}/.bazelrc\" \"$@\"\n}\n\nfunction generate_build_id() {\n   # Considerations for a build identifier: It must be unique, it shouldn't break\n   # if we try multiple dailies in a day, and it would be nice if a textual sort\n   # would put newest releases last.\n   git_hash=$(echo \"$GITHUB_SHA\" | cut -c1-6)\n   date \"+daily-%Y-%m-%d-${git_hash}\"\n}\n\n# Pushes images and releases a binary to a specified bucket.\n# bucket: target GCS bucket to release to\n# name:  name of the release tar ball\n# labels: optional list of filename aliases for the release, these are one-line\n#   text files with the release name as a bucket local path\nfunction release_binary {\n  local bucket=\"$1\"\n  local name=\"$2\"\n\n  # This function is called from test and release pipelines. We (re)build the binary and push the\n  # app images here to ensure the app images which are referenced in the binary exist in the\n  # registry.\n  bazel_ci build \\\n      //src/bootstrap/cloud:crc-binary \\\n      //src/app_charts:push \\\n      //src/go/cmd/setup-robot:setup-robot.push\n\n  # The push scripts depends on binaries in the runfiles.\n  local oldPwd\n  oldPwd=$(pwd)\n  # The tag variable must be called 'TAG', see cloud-robotics/bazel/container_push.bzl\n  for t in latest ${DOCKER_TAG}; do\n    cd ${oldPwd}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh.runfiles/${RUNFILES_ROOT}\n    ${oldPwd}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh \\\n      --repository=\"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}/setup-robot\" \\\n      --tag=\"${t}\"\n\n    cd ${oldPwd}/bazel-bin/src/app_charts/push.runfiles/${RUNFILES_ROOT}\n    TAG=\"$t\" ${oldPwd}/bazel-bin/src/app_charts/push \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\"\n  done\n  cd ${oldPwd}\n\n  gcloud storage cp \\\n      --predefined-acl=publicRead \\\n      bazel-bin/src/bootstrap/cloud/crc-binary.tar.gz \\\n      \"gs://${bucket}/${name}.tar.gz\"\n\n  # Overwrite cache control as we want changes to run-install.sh and version files to be visible\n  # right away.\n  gcloud storage cp \\\n      --predefined-acl=publicRead \\\n      --cache-control=\"private, max-age=0, no-transform\" \\\n      src/bootstrap/cloud/run-install.sh \\\n      \"gs://${bucket}/\"\n\n  # The remaining arguments are version labels. GCS does not support symlinks, so we use version\n  # files instead.\n  local vfile\n  vfile=$(mktemp)\n  echo \"${name}.tar.gz\" >${vfile}\n  shift 2\n  # Loop over remianing args in $* and creat alias files.\n  for label; do\n    gcloud storage cp \\\n        --predefined-acl=publicRead \\\n        --cache-control=\"private, max-age=0, no-transform\" \\\n        ${vfile} \"gs://${bucket}/${label}\"\n  done\n}\n\n\n"
  },
  {
    "path": ".github/ci/deploy_navtest.sh",
    "content": "#!/bin/bash\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\n\nPROJECT_DIR=\"${DIR}/deployments/robco-navtest\"\nsource \"${PROJECT_DIR}/config.sh\"\n\n# TODO(skopecki) These variables should be declared in the run-install.sh and removed from this script.\nexport BUCKET_URI=\"https://storage.googleapis.com/robco-ci-binary-builds\"\nexport SOURCE_CONTAINER_REGISTRY=\"gcr.io/robco-team\"\n\n# Deploy the binary release that was pushed by the last successful integration test.\ncurl --silent --show-error --fail \"${BUCKET_URI}/run-install.sh\" \\\n    | bash -x -s -- ${GCP_PROJECT_ID}\n\n"
  },
  {
    "path": ".github/ci/deploy_navtest_cloudbuild.yaml",
    "content": "# Call deploy_navtest.sh on Cloud Build.\n# TODO(b/323509860): Run directly on the Action runner when it supports WIF.\nsteps:\n  - name: \"gcr.io/cloud-builders/gcloud\"\n    entrypoint: \"bash\"\n    args: [\"./.github/ci/deploy_navtest.sh\"]\ntimeout: 1200s\n"
  },
  {
    "path": ".github/ci/deployments/robco-integration-test/config.sh",
    "content": "#!/usr/bin/env bash\n\n# Enable cloud robotics layer 2\nAPP_MANAGEMENT=true\n\nGCP_PROJECT_ID=robco-integration-test\nGCP_REGION=europe-west1\nGCP_ZONE=europe-west1-c\nCLOUD_ROBOTICS_SHARED_OWNER_GROUP=cloud-robotics-cloud-owner-acl@twosync.google.com\nCLOUD_ROBOTICS_DEPLOY_ENVIRONMENT=GCP-testing\nTERRAFORM_GCS_BUCKET=\"robco-team-terraform-state\"\nTERRAFORM_GCS_PREFIX=\"state/${GCP_PROJECT_ID}\"\nCLOUD_ROBOTICS_CONTAINER_REGISTRY=gcr.io/robco-team\nPRIVATE_DOCKER_PROJECTS=robco-team\nCLOUD_ROBOTICS_CTX=gke_robco-integration-test_europe-west1-c_cloud-robotics\n"
  },
  {
    "path": ".github/ci/deployments/robco-integration-test/kubernetes/k8s-relay-rollout.yaml",
    "content": "apiVersion: apps.cloudrobotics.com/v1alpha1\nkind: AppRollout\nmetadata:\n  name: k8s-relay\n  labels:\n    app: k8s-relay\nspec:\n  appName: k8s-relay-dev\n  cloud: {}\n  robots:\n  - selector:\n      any: true\n"
  },
  {
    "path": ".github/ci/deployments/robco-navtest/config.sh",
    "content": "#!/usr/bin/env bash\n\n# Enable google cloud robotics layer 2\nAPP_MANAGEMENT=true\n\nGCP_PROJECT_ID=robco-navtest\nGCP_REGION=europe-west1\nGCP_ZONE=europe-west1-c\nCLOUD_ROBOTICS_SHARED_OWNER_GROUP=cloud-robotics-cloud-owner-acl@twosync.google.com\nTERRAFORM_GCS_BUCKET=\"robco-team-terraform-state\"\nTERRAFORM_GCS_PREFIX=\"state/${GCP_PROJECT_ID}\"\nCLOUD_ROBOTICS_CONTAINER_REGISTRY=gcr.io/robco-team\nPRIVATE_DOCKER_PROJECTS=robco-team\nCLOUD_ROBOTICS_CTX=gke_robco-navtest_europe-west1-c_cloud-robotics\n"
  },
  {
    "path": ".github/ci/integration_test.sh",
    "content": "#!/bin/bash\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\nsource \"./scripts/common.sh\"\n\n# Because the format from common.sh is not recognized by Cloud Build.\nexport 'PS4='\n\n# Need to source the project config from here\nPROJECT_DIR=\"${DIR}/deployments/robco-integration-test\"\nsource \"${PROJECT_DIR}/config.sh\"\ngcloud config set project ${GCP_PROJECT_ID}\ngke_get_credentials \"${GCP_PROJECT_ID}\" \"cloud-robotics\" \"${GCP_REGION}\" \"${GCP_ZONE}\"\n\nBUILD_IDENTIFIER=$(generate_build_id)\necho \"INFO: Build identifier is $BUILD_IDENTIFIER\"\n\nexport BAZEL_FLAGS=\"--bazelrc=${DIR}/.bazelrc\"\nbash -x ./deploy.sh update \"${GCP_PROJECT_ID}\"\n\n# Create a GKE cluster with a single robot for the relay test.\nROBOT_RELAY_CLUSTER=\"relay-test\"\nexport SKIP_LOCAL_PULL=true\nbash -x ./scripts/robot-sim.sh create \"${GCP_PROJECT_ID}\" \"${ROBOT_RELAY_CLUSTER}\"\n\nbazel_ci run //src/go/cmd/setup-dev -- --project=\"${GCP_PROJECT_ID}\" --robot-name=\"${ROBOT_RELAY_CLUSTER}\"\n\nDOMAIN=${CLOUD_ROBOTICS_DOMAIN:-\"www.endpoints.${GCP_PROJECT_ID}.cloud.goog\"}\nROBOT_CONTEXT=\"gke_${GCP_PROJECT_ID}_${GCP_ZONE}_${ROBOT_RELAY_CLUSTER}\"\n\n# Output state of cloud and robot k8s context to inspect the health of pods.\nkubectl config get-contexts || true\nkubectl --context ${CLOUD_ROBOTICS_CTX} get pods || true\nkubectl --context ${GCP_PROJECT_ID}-robot get pods || true\nkubectl --context ${ROBOT_CONTEXT} get pods || true\n\nbazel_ci test \\\n  --test_env GCP_PROJECT_ID=${GCP_PROJECT_ID} \\\n  --test_env GCP_REGION=${GCP_REGION} \\\n  --test_env GCP_ZONE=${GCP_ZONE} \\\n  --test_env CLUSTER=${ROBOT_RELAY_CLUSTER} \\\n  --test_env PATH=$PATH \\\n  --jvmopt=\"-DCLOUD_ROBOTICS_DOMAIN=${DOMAIN}\" \\\n  --test_output=streamed \\\n  --test_tag_filters=\"external\" \\\n  --strategy=TestRunner=standalone \\\n  //...\n\n# If this is running on main (ie, not a manual run) then update the `latest`\n# binary.\nif [[ \"$MANUAL_RUN\" == \"false\" ]] ; then\n  release_binary \"robco-ci-binary-builds\" \"crc-${BUILD_IDENTIFIER}\" \"latest\"\nfi\n"
  },
  {
    "path": ".github/ci/integration_test_cloudbuild.yaml",
    "content": "# A Cloud Build job for running integration_test.sh.\n# TODO(b/323509860): Run directly on the Action runner when it supports WIF.\nsteps:\n  # Needed for cloud build to allow running Bazel as non-root, see\n  # https://github.com/GoogleCloudPlatform/cloud-builders/issues/641#issuecomment-604599102\n  # Not part of the Dockerfile since the chmod layer adds significant image size.\n  - name: ubuntu\n    entrypoint: \"bash\"\n    args: [\"-c\", \"chmod -R 777 /builder && chmod -R 777 /workspace\"]\n\n  # This runs on a custom image that has kubectl, gcloud and bazel installed.\n  # See Dockerfile.integration-test-image.\n  - name: \"gcr.io/robco-integration-test/integration-test-image@sha256:87e7cde1d2923eed014ee8ee0c365d683fa7a8a99985bbc77acb951c2e6faefc\"\n    entrypoint: \"bash\"\n    args: [\"./.github/ci/integration_test.sh\"]\n    env:\n      - \"GITHUB_SHA=${_GITHUB_SHA}\"\n      - \"MANUAL_RUN=${_MANUAL_RUN}\"\n\nsubstitutions:\n  _GITHUB_SHA: \"\"\n  _MANUAL_RUN: \"\"\n\noptions:\n  dynamicSubstitutions: true\n  substitutionOption: \"MUST_MATCH\"\ntimeout: 1800s\n"
  },
  {
    "path": ".github/ci/integration_test_image_builder.sh",
    "content": "#!/bin/bash\n# Builds and pushes a docker image that can be used in Cloud Build to run\n# the integration test (see integration_test_cloudbuild.yaml).\n#\n# To be manually invoked and the resulting sha256 copied to\n# integration_test_cloudbuild.yaml after changing the Dockerfile.\n\nset -euo pipefail\n\nNAME=\"gcr.io/robco-integration-test/integration-test-image\"\ndocker build --network=host -t \"${NAME}\" - \\\n  < .github/ci/Dockerfile.integration-test-image\ndocker push \"${NAME}\"\n"
  },
  {
    "path": ".github/ci/presubmit.sh",
    "content": "#!/bin/bash\n#\n# Presubmit script for testing cloud robotics.\n# Expected to run remotely on a GitHub Actions runner, not locally.\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\n# shellcheck source=ci/common.sh\nsource \"${DIR}/common.sh\"\n\necho \"Timestamp: build started\"\nbazel_ci build --nobuild  //...\necho \"Timestamp: build-deps fetched\"\nbazel_ci build //...\necho \"Timestamp: build done\"\nbazel_ci test --test_output=errors //...\necho \"Timestamp: test done\"\n\n# Some of the tests below pull Docker images from the repository. We need to\n# make sure they are pushed and provide an access token.\ngcloud auth configure-docker --quiet\n\nREGISTRY=\"gcr.io/robco-integration-test\"\nTAG=\"latest\" bazel_ci run \\\n  //src/app_charts:push \"${REGISTRY}\"\n\n# We're running into timeouts at CI and also don't see the actual failure\n# reasons. Disable the test for now until someone has time and ideas how to\n# resurrect it.\n#\n# set +o xtrace  # Don't put the access token in the logs.\n# ACCESS_TOKEN=\"$(gcloud auth application-default print-access-token)\"\n# Note: --strategy=TestRunner=standalone means that the tests are run locally\n# and not on a remote worker (which does not have the Docker environment).\n# bazel_ci test \\\n#   --flaky_test_attempts 3 \\\n#   --test_env ACCESS_TOKEN=\"${ACCESS_TOKEN}\" \\\n#   --test_env REGISTRY=\"${REGISTRY}\" \\\n#   --test_tag_filters=\"requires-docker\" \\\n#   --test_output=errors \\\n#   --strategy=TestRunner=standalone //src/go/tests/apps:go_default_test\n# \n# set -o xtrace\n\necho \"Timestamp: presubmit.sh done\"\n"
  },
  {
    "path": ".github/ci/release_binary.sh",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n# shellcheck source=ci/common.sh\nsource \"${DIR}/common.sh\"\n\ngcloud auth configure-docker --quiet\n\n# Set defaults used for the release, they can only be overriden when testing\n# manually.\nGCP_BUCKET=${GCP_BUCKET:-\"cloud-robotics-releases\"}\nVERSION=${VERSION:-\"0.1.0\"}\nSHA=$(git rev-parse --short \"$FULL_SHA\")\nRELEASE_NAME=\"v$VERSION-$SHA\"\n# TAG is a global variable that is used in the container push rules.\nexport TAG=\"crc-${VERSION}-${SHA}\"\nLABELS=${LABELS:-\"latest crc-${VERSION}/crc-${VERSION}+latest\"}\n\n# Get the last release. We only create a new release if the main branch has moved since\n# as trying to re-create an existing release is an error.\noutput=$(curl --fail-with-body -sS \\\n  -H \"Accept: application/vnd.github+json\" \\\n  -H \"Authorization: token $GITHUB_TOKEN\" \\\n  https://api.github.com/repos/$REPO/releases/latest)\nPREVIOUS_RELEASE_NAME=\"$(jq -r '.tag_name'   <<< $output)\"\n\nif [ \"$RELEASE_NAME\" = \"$PREVIOUS_RELEASE_NAME\" ]; then\n    echo \"Release $RELEASE_NAME already exists. Nothing more to do.\"\n    exit 0\nelse\n    echo \"Previous release is $PREVIOUS_RELEASE_NAME\"\nfi\n\nCLOUD_ROBOTICS_CONTAINER_REGISTRY=\"gcr.io/cloud-robotics-releases\"\n# DOCKER_TAG is a global variable that is used in release_binary.\nDOCKER_TAG=${DOCKER_TAG:-\"crc-${VERSION}-${SHA}\"}\n\nrelease_binary \"${GCP_BUCKET}\" \"crc-${VERSION}/crc-${VERSION}+${SHA}\" ${LABELS}\n\n# Generate release notes comparing against the previous release.\noutput=$(curl --fail-with-body -sS \\\n  -X POST \\\n  -H \"Accept: application/vnd.github+json\" \\\n  -H \"Authorization: token $GITHUB_TOKEN\" \\\n  https://api.github.com/repos/$REPO/releases/generate-notes \\\n  -d '{\"tag_name\":\"'$RELEASE_NAME'\",\"previous_tag_name\":\"'$PREVIOUS_RELEASE_NAME'\"}')\n# Code newlines as literal \\n and escape double quotes to generate valid JSON.\nBODY=\"$(jq -r '.body'   <<< $output | awk '{printf \"%s\\\\n\", $0}' | sed 's/\"/\\\\\"/g')\"\necho \"Generated release notes for $RELEASE_NAME\"\n\n# Create the release on GitHub.\ncurl --fail-with-body -sS \\\n  -X POST \\\n  -H \"Accept: application/vnd.github+json\" \\\n  -H \"Authorization: token $GITHUB_TOKEN\" \\\n  https://api.github.com/repos/$REPO/releases \\\n  --data-binary @- << EOF\n{\n  \"tag_name\": \"$RELEASE_NAME\",\n  \"name\": \"$RELEASE_NAME\",\n  \"body\": \"$BODY\"\n}\nEOF\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/src/\"\n    schedule:\n      interval: \"weekly\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n\n  - package-ecosystem: \"bazel\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/check-bazel.yml",
    "content": "name: Check Bazel\n\non:\n  workflow_call:\n\npermissions:\n  contents: read\n  id-token: write\n\njobs:\n  check-bazel:\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2\n      - name: Auth\n        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # tag=v3.0.0\n        with:\n          create_credentials_file: true # also sets GOOGLE_APPLICATION_CREDENTIALS\n          service_account: \"github-automation-bot@gha-crc-dev.iam.gserviceaccount.com\"\n          workload_identity_provider: \"projects/1043719249528/locations/global/workloadIdentityPools/github-automation/providers/crc-dev\"\n      - name: Print error on auth fail\n        if: failure()\n        run: |\n          echo >&2 \"This PR appears to be from a fork or authored by a non-org member, rather than from the primary repo.\"\n          echo >&2 \"This means it can't run the presubmit, which requires access to GCR.\"\n          echo >&2 \"If you are a project member, please push your branch to github.com/googlecloudrobotics/core instead.\"\n          exit 1\n      - name: Run .github/ci/presubmit.sh\n        run: ./.github/ci/presubmit.sh\n      - name: Get bazel server logs\n        if: success() || failure()\n        run: cat ~/.cache/bazel/_bazel_*/*/java.log || true\n"
  },
  {
    "path": ".github/workflows/postsubmit.yml",
    "content": "name: Postsubmit\n\non:\n  schedule:\n    - cron: \"0 4 * * *\" # Once a day at 4am.\n  # Manual runs through Actions tab in the UI\n  workflow_dispatch:\n    inputs:\n      force-binary-release:\n        description: >-\n          force-binary-release: Set to non-empty when running from main to\n          create a binary release that can be used by 'Create release'.\n\npermissions:\n  contents: read\n  id-token: write\n\nconcurrency:\n  group: integration_test\n  cancel-in-progress: true\n\njobs:\n  call-bazel:\n    uses: ./.github/workflows/check-bazel.yml\n\n  integration-test:\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2\n      - name: Auth\n        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # tag=v3.0.0\n        with:\n          create_credentials_file: true # also sets GOOGLE_APPLICATION_CREDENTIALS\n          service_account: \"github-automation-bot@gha-crc-dev.iam.gserviceaccount.com\"\n          workload_identity_provider: \"projects/1043719249528/locations/global/workloadIdentityPools/github-automation/providers/crc-dev\"\n      - name: Run integration_test.sh on Cloud Build\n        env:\n          MANUAL_RUN: \"${{ github.event_name == 'workflow_dispatch' && inputs.force-binary-release == '' }}\"\n        run: |\n          gcloud builds submit \\\n            --project robco-integration-test \\\n            --region europe-west1 \\\n            --config .github/ci/integration_test_cloudbuild.yaml \\\n            --substitutions _GITHUB_SHA=${GITHUB_SHA},_MANUAL_RUN=${MANUAL_RUN}\n"
  },
  {
    "path": ".github/workflows/presubmit.yml",
    "content": "name: Presubmit\n\non:\n  pull_request:\n    branches: [\"main\"]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  id-token: write\n  pull-requests: read\n\n# Cancel previous runs if a new one is started.\nconcurrency:\n  group: ${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  setup-presubmit:\n    runs-on: ubuntu-22.04\n    outputs:\n      presubmit_digest: ${{ steps.pr-digest.outputs.digest }}\n      presubmit_status: ${{ steps.status.outputs.status }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2\n      - name: Get PR digest\n        id: pr-digest\n        env:\n          GH_TOKEN: ${{ github.token }}\n        run: |\n          set -euo pipefail\n\n          # Get the list of changed files in the PR.\n          gh pr view ${{github.event.number}} --json files -q '.files[].path' > /tmp/changed_files.txt\n\n          # Create a tarball of the changed files and compute its SHA256.\n          # --ignore-failed-read to not fail on deleted files.\n          tar -cvf /tmp/changed_files.tar \\\n            --owner=root --group=root --numeric-owner --mtime=\"2010-01-01\" --sort=name \\\n            -T /tmp/changed_files.txt \\\n            --ignore-failed-read\n          digest=$(cat /tmp/changed_files.txt /tmp/changed_files.tar | sha256sum | cut -d \" \" -f1)\n          echo \"digest=$digest\" >> $GITHUB_OUTPUT\n      - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: ~/PRESUBMITS_SUCCEEDED\n          key: PRESUBMITS_SUCCEEDED-${{ steps.pr-digest.outputs.digest }}\n      - name: Check for previous runs\n        id: status\n        run: |\n          if [ -f ~/PRESUBMITS_SUCCEEDED ]; then\n            echo \"status=success\" >> $GITHUB_OUTPUT\n          fi\n\n  call-check-bazel:\n    needs: [setup-presubmit]\n    if: ${{ needs.setup-presubmit.outputs.presubmit_status != 'success' }}\n    uses: ./.github/workflows/check-bazel.yml\n\n  presubmits-ok:\n    needs: [setup-presubmit, call-check-bazel]\n    runs-on: ubuntu-22.04\n    # To ensure this job always runs even if the \"heavy\" jobs were skipped.\n    # This allows us to guard merging on this check in Branch Protection.\n    if: ${{ always() }}\n    steps:\n      - name: Fail if tests failed\n        # always() because GitHub requires a status macro to be included or else this gets skipped.\n        # https://github.com/actions/runner/issues/491#issuecomment-850884422\n        if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}\n        run: exit 1\n      - name: Create presubmits succeeded marker\n        run: touch ~/PRESUBMITS_SUCCEEDED\n      - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: ~/PRESUBMITS_SUCCEEDED\n          key: PRESUBMITS_SUCCEEDED-${{ needs.setup-presubmit.outputs.presubmit_digest }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Create release\n\non:\n  schedule:\n    - cron: \"0 5 * * *\" # Once a day at 5am.\n  # Manual runs through Actions tab in the UI\n  workflow_dispatch:\n\npermissions:\n  actions: read\n  contents: write\n  id-token: write\n  pull-requests: read\n\n# Cancel previous runs if a new one is started.\nconcurrency:\n  group: ${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  create_release:\n    runs-on: ubuntu-22.04\n    steps:\n      # Check out repo at latest green postsubmit commit on the main branch.\n      - name: Get latest passing commit\n        id: latest-green\n        env:\n          REPO: ${{ github.repository }}\n        run: |\n          set -euo pipefail\n          output=$(curl --fail-with-body -sS \\\n            -H \"Accept: application/vnd.github+json\" \\\n            \"https://api.github.com/repos/$REPO/actions/workflows/postsubmit.yml/runs?per_page=1&branch=main&event=schedule&status=success\")\n          repo_id=$(jq -r '.workflow_runs[0].head_repository.id' <<< $output)\n          if [[ \"${repo_id}\" != \"${{ github.repository_id }}\" ]] ; then\n            echo >&2 \"Unexpected head repository ID: ${repo_id} - check postsubmit.yml configuration\"\n            exit 1\n          fi\n          sha=$(jq -r '.workflow_runs[0].head_sha' <<< $output)\n          echo \"latest_green=$sha\" >> $GITHUB_OUTPUT\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2\n        with:\n          ref: ${{ steps.latest-green.outputs.latest_green }}\n      - name: Auth\n        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # tag=v3.0.0\n        with:\n          create_credentials_file: true # also sets GOOGLE_APPLICATION_CREDENTIALS\n          service_account: \"github-automation-bot@gha-crc-prod.iam.gserviceaccount.com\"\n          workload_identity_provider: \"projects/695270090783/locations/global/workloadIdentityPools/github-automation/providers/crc-prod\"\n      - name: \"Set up gcloud\"\n        uses: \"google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db\" # tag=v3.0.1\n        with:\n          skip_install: true\n\n      - name: Deploy Navtest on Cloud Build\n        run: |\n          gcloud builds submit \\\n            --project robco-navtest \\\n            --config .github/ci/deploy_navtest_cloudbuild.yaml\n\n      # Now we are ready to create the release.\n      - name: Run release_binary.sh\n        env:\n          REPO: ${{ github.repository }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          FULL_SHA: ${{ steps.latest-green.outputs.latest_green }}\n        run: ./.github/ci/release_binary.sh\n"
  },
  {
    "path": ".gitignore",
    "content": ".cache/\n*.pyc\nenv/\n\n# auto-generated by scripts/backport_kubeadm.sh\nnsenter\n\n# IntelliJ files\n.project/\n\n# Bazel\nbazel-*\nbuildprofile.out\nbuildprofile.out.html\n"
  },
  {
    "path": ".pep8",
    "content": "[pep8]\naggressive=2\nindent-size=2\nmax-line-length=80\n"
  },
  {
    "path": "BUILD.bazel",
    "content": "# Description:\n#   Root BUILD file for cloud-robotics\n\nload(\"@bazel_gazelle//:def.bzl\", \"gazelle\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files([\n    \"config.sh.tmpl\",\n    \"deploy.sh\",\n])\n\n# Gazelle uses this to build importpath attributes.\n# gazelle:prefix github.com/googlecloudrobotics/core\n\n# Gazelle is used to generate BUILD.bazel files for WORKSPACE dependencies\n# running this manually via \"bazel run //:gazelle\" will regenerate BUILD.bazel files that\n# contain go-rules.\ngazelle(\n    name = \"gazelle\",\n)\n\n# Libraries are named go_default_library, tests are named go_default_test.\n# gazelle:go_naming_convention go_default_library\n\n# We ignore the build files generated by bazel-deps as it doesn't use buildifer.\n# gazelle:exclude third_party\n\n# Also ignore the Go sources downloaded by src/go/deps.sh.\n# gazelle:exclude src/.gopath\n\n# Don't created build files for these examples\n# gazelle:exclude  docs/how-to/examples/greeter-service/proto/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guidelines you need to follow.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution;\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Code reviews\n\nAll submissions, including submissions by project members, require review. We\nuse GitHub pull requests for this purpose. Consult\n[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more\ninformation on using pull requests.\n\n## Code formatting\n\nWe have a pre-commit hook to check code formatting, which you can install with:\n\n```\nln -s ../../scripts/pre-commit .git/hooks/\n```\n\nIt depends on external tools for formatting, which you may be prompted to\ninstall when it first runs.\n\n## Community Guidelines\n\nThis project follows\n[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).\n\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "METADATA",
    "content": "name: \"Cloud Robotics\"\n\nthird_party {\n  # https://nvd.nist.gov/products/cpe/search\n  security {\n    tag: \"NVD-CPE2.3:cpe:/a:grafana:grafana:10.3.1\"\n    tag: \"NVD-CPE2.3:cpe:/a:kubernetes:ingress-nginx:1.8.4\"\n    tag: \"NVD-CPE2.3:cpe:/a:oauth2_proxy_project:oauth2_proxy:7.5.1\"\n    tag: \"NVD-CPE2.3:cpe:/a:prometheus:prometheus:2.49.1\"\n  }\n}\n"
  },
  {
    "path": "MODULE.bazel",
    "content": "bazel_dep(name = \"aspect_bazel_lib\", version = \"2.21.2\")\nbazel_dep(name = \"bazel_skylib\", version = \"1.8.1\")\nbazel_dep(name = \"platforms\", version = \"1.0.0\")\nbazel_dep(name = \"protobuf\", version = \"29.0\", repo_name = \"com_google_protobuf\")\nbazel_dep(name = \"rules_oci\", version = \"2.0.1\")\nbazel_dep(name = \"rules_pkg\", version = \"1.0.1\")\nbazel_dep(name = \"rules_shell\", version = \"0.4.1\")\n# -- bazel_dep definitions -- #\n\nnon_module_deps = use_extension(\"//:non_module_deps.bzl\", \"non_module_deps\")\nuse_repo(non_module_deps, \"kubernetes_helm\")\nuse_repo(non_module_deps, \"kubernetes_helm3\")\nuse_repo(non_module_deps, \"hashicorp_terraform\")\nuse_repo(non_module_deps, \"com_github_kubernetes_sigs_application\")\nuse_repo(non_module_deps, \"ingress-nginx\")\n# End of extension `non_module_deps`\n\n#######\n# C++ #\n#######\n\nbazel_dep(name = \"toolchains_llvm\", version = \"1.7.0\")\n\n# Inspect supported toolchains at https://github.com/bazel-contrib/toolchains_llvm/blob/master/toolchain/internal/llvm_distributions.bzl\nllvm = use_extension(\"@toolchains_llvm//toolchain/extensions:llvm.bzl\", \"llvm\")\nllvm.toolchain(\n    llvm_version = \"18.1.4\",\n)\n\nuse_repo(non_module_deps, \"com_googleapis_storage_chrome_linux_amd64_sysroot\")\n\nllvm.sysroot(\n    label = \"@com_googleapis_storage_chrome_linux_amd64_sysroot//:all_files\",\n    targets = [\"linux-x86_64\"],\n)\nuse_repo(llvm, \"llvm_toolchain\")\n\nregister_toolchains(\"@llvm_toolchain//:all\")\n\nbazel_dep(name = \"rules_cc\", version = \"0.1.5\")\n\n######\n# Go #\n######\n\nbazel_dep(name = \"rules_go\", version = \"0.59.0\", repo_name = \"io_bazel_rules_go\")\n\ngo_sdk = use_extension(\"@io_bazel_rules_go//go:extensions.bzl\", \"go_sdk\")\ngo_sdk.download(version = \"1.25.4\")\n\nbazel_dep(name = \"gazelle\", version = \"0.47.0\", repo_name = \"bazel_gazelle\")\n\ngo_deps = use_extension(\"@bazel_gazelle//:extensions.bzl\", \"go_deps\")\ngo_deps.from_file(go_mod = \"//src:go.mod\")\nuse_repo(\n    go_deps,\n    \"com_github_cenkalti_backoff\",\n    \"com_github_form3tech_oss_jwt_go\",\n    \"com_github_fsnotify_fsnotify\",\n    \"com_github_getlantern_httptest\",\n    \"com_github_golang_glog\",\n    \"com_github_golang_mock\",\n    \"com_github_google_go_cmp\",\n    \"com_github_google_nftables\",\n    \"com_github_googlecloudrobotics_ilog\",\n    \"com_github_jaypipes_ghw\",\n    \"com_github_jaypipes_pcidb\",\n    \"com_github_motemen_go_loghttp\",\n    \"com_github_onsi_gomega\",\n    \"com_github_pkg_errors\",\n    \"com_github_prometheus_client_golang\",\n    \"com_github_spf13_cobra\",\n    \"com_github_spf13_pflag\",\n    \"com_google_cloud_go_compute_metadata\",\n    \"com_google_cloud_go_storage\",\n    \"in_gopkg_h2non_gock_v1\",\n    \"io_k8s_api\",\n    \"io_k8s_apiextensions_apiserver\",\n    \"io_k8s_apimachinery\",\n    \"io_k8s_cli_runtime\",\n    \"io_k8s_client_go\",\n    \"io_k8s_helm\",\n    \"io_k8s_klog\",\n    \"io_k8s_klog_v2\",\n    \"io_k8s_sigs_controller_runtime\",\n    \"io_k8s_sigs_kind\",\n    \"io_k8s_sigs_yaml\",\n    \"io_opencensus_go\",\n    \"io_opencensus_go_contrib_exporter_prometheus\",\n    \"io_opencensus_go_contrib_exporter_stackdriver\",\n    \"org_golang_google_api\",\n    \"org_golang_google_grpc\",\n    \"org_golang_google_protobuf\",\n    \"org_golang_x_crypto\",\n    \"org_golang_x_net\",\n    \"org_golang_x_oauth2\",\n    \"org_golang_x_sync\",\n)\n\n#######\n# OCI #\n#######\n\noci = use_extension(\"@rules_oci//oci:extensions.bzl\", \"oci\")\n\n# gcloud container images describe gcr.io/distroless/base:latest --format='value(image_summary.digest)'\noci.pull(\n    name = \"distroless_base\",\n    digest = \"sha256:b31a6e02605827e77b7ebb82a0ac9669ec51091edd62c2c076175e05556f4ab9\",\n    image = \"gcr.io/distroless/base\",\n    platforms = [\"linux/amd64\"],\n)\n\n# gcloud container images describe gcr.io/distroless/cc:latest --format='value(image_summary.digest)'\noci.pull(\n    name = \"distroless_cc\",\n    digest = \"sha256:8aad707f96620ee89e27febef51b01c6ff244277a3560fcfcfbe68633ef09193\",\n    image = \"gcr.io/distroless/cc\",\n    platforms = [\"linux/amd64\"],\n)\noci.pull(\n    name = \"iptables_base\",\n    digest = \"sha256:656e45c00083359107b1d6ae0411ff3894ba23011a8533e229937a71be84e063\",\n    image = \"gcr.io/google-containers/debian-iptables\",\n    platforms = [\"linux/amd64\"],\n)\nuse_repo(\n    oci,\n    \"distroless_base\",\n    \"distroless_base_linux_amd64\",\n    \"distroless_cc\",\n    \"distroless_cc_linux_amd64\",\n    \"iptables_base\",\n    \"iptables_base_linux_amd64\",\n)\n"
  },
  {
    "path": "README.md",
    "content": "# Cloud Robotics Core\n\nGoogle's Cloud Robotics Core is an open source platform that provides\ninfrastructure essential to building and running robotics solutions for business\nautomation. Cloud Robotics Core makes managing robot fleets easy for developers,\nintegrators, and operators. It enables:\n\n* packaging and distribution of applications\n* secure, bidirectional robot-cloud communication\n* easy access to Google Cloud services such as ML, logging, and monitoring.\n\n![Cloud Robotics Core overview](docs/cloud-robotics-core-overview.png)\n\nCloud Robotics Core is open source and pre-alpha. Support is currently limited\nto a small set of early access partners. We will gladly accept contributions\nand feedback, but we are making no stability or support guarantees at this\npoint in time.\n\n# Documentation\n\nDocumentation of the platform and related How-to guides can be found at: https://googlecloudrobotics.github.io/core/\n\n# Get Involved\n\nIf you want to get involved, please refer to [CONTRIBUTING.md](CONTRIBUTING.md),\nreach out to [cloud-robotics-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/cloud-robotics-discuss)\nor ask Stack Overflow questions with [#google-cloud-robotics](https://stackoverflow.com/questions/tagged/google-cloud-robotics).\n\n# Source Code\n\nMost interesting bits are under `src`:\n\n* app_charts: contains kubernetes resources for the core platform and apps\n* bootstrap: provisioning for the cloud (terraform) and the robot (debian package)\n* go/: the code that goes into images referenced from `app_charts`\n\nThe root directory contains a `deploy.sh` script for building and installing the software. More\ndetails on that are in the [building from sources](how-to/deploy-from-sources) guide.\n"
  },
  {
    "path": "bazel/BUILD.bazel",
    "content": "exports_files([\n    \"app.bzl\",\n    \"app_chart.bzl\",\n    \"container_push.bzl\",\n    \"repositories.bzl\",\n])\n\nplatform(\n    name = \"linux_x86_64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n        \"@bazel_tools//tools/cpp:clang\",\n    ],\n    exec_properties = {\n        \"container-image\": \"docker://gcr.io/cloud-robotics-releases/bazel-rbe-executor@sha256:3ee043e7a322caaff8c9edaa302373deb80e67ad6e42ae35d34b8f3597b8995e\",\n        \"OSFamily\": \"Linux\",\n    },\n    parents = [\"@local_config_platform//:host\"],\n)\n"
  },
  {
    "path": "bazel/BUILD.sysroot",
    "content": "filegroup(\n    name = \"all_files\",\n    srcs = glob(\n        [\n            \"lib/x86_64-linux-gnu/ld*\",\n            \"lib/x86_64-linux-gnu/libc*\",\n            \"lib/x86_64-linux-gnu/libdl*\",\n            \"lib/x86_64-linux-gnu/libgcc*\",\n            \"lib/x86_64-linux-gnu/libm*\",\n            \"lib/x86_64-linux-gnu/libpthread*\",\n            \"lib/x86_64-linux-gnu/librt*\",\n            \"lib/x86_64-linux-gnu/libutil*\",\n            \"lib64/**\",\n            \"usr/include/*.h\",\n            \"usr/include/arpa/**\",\n            \"usr/include/asm-generic/**\",\n            \"usr/include/c++/**\",\n            \"usr/include/linux/**\",\n            \"usr/include/net/**\",\n            \"usr/include/netinet/**\",\n            \"usr/include/rpc/**\",\n            \"usr/include/sys/**\",\n            \"usr/include/x86_64-linux-gnu/**\",\n            \"usr/lib/gcc/**\",\n            \"usr/lib/x86_64-linux-gnu/*crt*.o\",\n            \"usr/lib/x86_64-linux-gnu/libc_nonshared.a\",\n            \"usr/lib/x86_64-linux-gnu/libc.a\",\n            \"usr/lib/x86_64-linux-gnu/libc.so\",\n            \"usr/lib/x86_64-linux-gnu/libdl*\",\n            \"usr/lib/x86_64-linux-gnu/libm*\",\n            \"usr/lib/x86_64-linux-gnu/libpthread*\",\n            \"usr/lib/x86_64-linux-gnu/libresolv.so\",\n            \"usr/lib/x86_64-linux-gnu/librt*\",\n            \"usr/lib/x86_64-linux-gnu/libutil*\",\n        ],\n    ),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "bazel/app.bzl",
    "content": "load(\"//bazel/build_rules/app_chart:run_parallel.bzl\", \"run_parallel\")\n\ndef app(name, charts, visibility = None):\n    \"\"\"Macro for a standard Cloud Robotics app.\n\n    This macro establishes two subrules for app name \"foo\":\n    - :foo.push pushes the Docker images for the app.\n    - :foo.yaml is a YAML file with the app CR that you need to push to\n      Kubernetes. Use k8s_object to push it, or compile it into a Helm chart.\n\n    Args:\n      name: string. Name of the app.\n      charts: list of targets. Helm charts for this app.\n      visibility: Visibility.\n    \"\"\"\n    pkg = Label(\"{}//{}\".format(native.repository_name(), native.package_name()))\n    chart_labels = [pkg.relative(c) for c in charts]\n    run_parallel(\n        name = name + \".push\",\n        targets = [\"//{}:{}.push\".format(c.package, c.name) for c in chart_labels],\n        visibility = visibility,\n    )\n\n    native.genrule(\n        # we name this differently than the file we produce to silence:\n        #   target 'xxx.yaml' is both a rule and a file; please choose another name for the rule\n        name = name + \".manifest\",\n        srcs = [\n            \"//{}:{}.snippet-yaml\".format(c.package, c.name)\n            for c in chart_labels\n        ],\n        outs = [name + \".yaml\"],\n        cmd = \"\"\"cat - $(SRCS) > $@ <<EOF\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: App\nmetadata:\n  name: {name}-dev\nspec:\n  components:\nEOF\n\"\"\".format(name = name),\n        visibility = visibility,\n    )\n"
  },
  {
    "path": "bazel/app_chart.bzl",
    "content": "load(\"//bazel:build_rules/helm_chart.bzl\", \"helm_chart\")\nload(\"//bazel/build_rules/app_chart:cache_gcr_credentials.bzl\", \"cache_gcr_credentials\")\nload(\"//bazel/build_rules/app_chart:push_all.bzl\", \"push_all\")\n\ndef _impl(ctx):\n    chart_yaml = ctx.actions.declare_file(ctx.label.name + \"-chart.yaml\")\n\n    ctx.actions.expand_template(\n        template = ctx.file._chart_yaml_template,\n        output = chart_yaml,\n        substitutions = {\"${name}\": ctx.label.name, \"${version}\": \"0.0.1\"},\n    )\n\n    values_yaml = ctx.actions.declare_file(ctx.label.name + \"-values.yaml\")\n    source_digests = []\n    cmds = [\n        \"cat {} - > {} <<EOF\".format(ctx.file.values.path, values_yaml.path),\n        \"### Generated by app_chart ###\",\n        \"images:\",\n    ]\n    jq_bin = ctx.toolchains[\"@aspect_bazel_lib//lib:jq_toolchain_type\"].jqinfo.bin\n\n    images = ctx.attr.images or {}\n    for key, value in images.items():\n        key_files = key[DefaultInfo].files\n        for file in key_files.to_list():\n            image_dir = file\n\n        # keep the leading '/' since helm charts prepend the registry without one\n        cmds.append(\"  {nick}: /{image}@$({jq} -r '.manifests[0].digest' {digest}/index.json)\".format(\n            nick = value.replace(\"-\", \"_\"),\n            image = value,\n            digest = image_dir.path,\n            jq = jq_bin.path,\n        ))\n        source_digests.append(image_dir)\n    cmds.append(\"EOF\")\n\n    ctx.actions.run_shell(\n        tools = [jq_bin],\n        outputs = [values_yaml],\n        inputs = ctx.files.values + source_digests,\n        command = \"\\n\".join(cmds),\n        toolchain = None,\n    )\n\n    helm_chart(\n        ctx,\n        name = ctx.label.name,\n        chart = chart_yaml,\n        values = values_yaml,\n        # TODO(b/72936439): This is currently unused and fixed to 0.0.1.\n        version = \"0.0.1\",\n        templates = ctx.files.templates,\n        files = ctx.files.files,\n        helm = ctx.file._helm,\n        out = ctx.outputs.chart,\n    )\n\n    return [DefaultInfo(\n        runfiles = ctx.runfiles(files = [ctx.outputs.chart]),\n        files = depset([ctx.outputs.chart]),\n    )]\n\n_app_chart_backend = rule(\n    implementation = _impl,\n    attrs = {\n        \"chart\": attr.string(\n            doc = \"the chart name (robot/cloud/cloud-per-robot)\",\n            mandatory = True,\n        ),\n        \"values\": attr.label(\n            allow_single_file = True,\n            doc = \"The values.yaml file.\",\n        ),\n        \"templates\": attr.label_list(\n            allow_empty = True,\n            allow_files = True,\n            default = [],\n            doc = \"Files for the chart's templates/ directory.\",\n        ),\n        \"files\": attr.label_list(\n            allow_empty = True,\n            allow_files = True,\n            default = [],\n            doc = \"Extra non-template files for the chart's files/ directory.\",\n        ),\n        \"images\": attr.label_keyed_string_dict(\n            allow_empty = True,\n            doc = \"Images referenced by the chart.\",\n        ),\n        \"_chart_yaml_template\": attr.label(\n            default = Label(\"//bazel/build_rules/app_chart:Chart.yaml.template\"),\n            allow_single_file = True,\n        ),\n        \"_helm\": attr.label(\n            default = Label(\"@kubernetes_helm//:helm\"),\n            allow_single_file = True,\n        ),\n    },\n    outputs = {\n        \"chart\": \"%{name}-0.0.1.tgz\",\n    },\n    toolchains = [\"@aspect_bazel_lib//lib:jq_toolchain_type\"],\n)\n\ndef app_chart(\n        name,\n        values = None,\n        extra_templates = None,\n        files = None,\n        images = None,\n        visibility = None):\n    \"\"\"Macro for a standard Cloud Robotics helm chart.\n\n    This macro establishes two subrules for chart name \"foo-cloud\":\n    - :foo-cloud.push pushes the Docker images for the chart (if relevant).\n    - :foo-cloud.snippet-yaml is a snippet of YAML defining the chart, which is\n      used by app() to generate an App CR containing multiple inline\n      charts.\n\n    Args:\n      name: string. Must be in the format {app}-{chart}, where chart is\n        robot, cloud, or cloud-per-robot.\n      values: file. The values.yaml file.\n      extra_templates: list of files. Extra files for the chart's templates/ directory.\n      files: list of files. Extra non-template files for the chart's files/ directory.\n      images: dict. Images referenced by the chart.\n      visibility: Visibility.\n    \"\"\"\n\n    _, chart = name.rsplit(\"-\", 1)\n    if name.endswith(\"cloud-per-robot\"):\n        chart = \"cloud-per-robot\"\n\n    if not values:\n        if chart == \"cloud\":\n            values = Label(\"//bazel/build_rules/app_chart:values-cloud.yaml\")\n        else:\n            values = Label(\"//bazel/build_rules/app_chart:values-robot.yaml\")\n\n    # We have a dict of string:target, but bazel rules only support target:string.\n    reversed_images = {}\n    if images:\n        for k, v in images.items():\n            reversed_images[v] = k\n\n    _app_chart_backend(\n        name = name,\n        chart = chart,\n        values = values,\n        templates = native.glob([chart + \"/*.yaml\"], allow_empty = True) + (extra_templates or []),\n        files = files,\n        images = reversed_images,\n        visibility = visibility,\n    )\n\n    push_all(\n        name = name + \".push-all-containers\",\n        images = images,\n    )\n\n    cache_gcr_credentials(\n        name = name + \".push\",\n        target = name + \".push-all-containers\",\n        visibility = visibility,\n    )\n\n    native.genrule(\n        name = name + \".snippet-yaml\",\n        srcs = [name],\n        outs = [name + \".snippet.yaml\"],\n        cmd = \"\"\"cat <<EOF > $@\n    {target}:\n      inline: $$(base64 -w 0 $<)\nEOF\n\"\"\".format(name = name, target = chart),\n    )\n"
  },
  {
    "path": "bazel/build_rules/app_chart/BUILD.bazel",
    "content": "exports_files([\n    \"cache_gcr_credentials.sh.tpl\",\n    \"Chart.yaml.template\",\n    \"push_all.sh.tpl\",\n    \"run_parallel.sh.tpl\",\n    \"values-cloud.yaml\",\n    \"values-robot.yaml\",\n])\n"
  },
  {
    "path": "bazel/build_rules/app_chart/Chart.yaml.template",
    "content": "apiVersion: v1\nname: ${name}\nversion: ${version}\n# Linter expects an icon.\nicon: https://google.com/icon.png\n"
  },
  {
    "path": "bazel/build_rules/app_chart/cache_gcr_credentials.bzl",
    "content": "def _get_runfile_path(ctx, f):\n    \"\"\"Return the runfiles relative path of f.\"\"\"\n    if ctx.workspace_name:\n        return \"${RUNFILES}/\" + ctx.workspace_name + \"/\" + f.short_path\n    else:\n        return \"${RUNFILES}/\" + f.short_path\n\ndef _impl(ctx):\n    runfiles = ctx.attr._sh_tpl.default_runfiles.files.to_list()\n    runfiles.append(ctx.attr.target.files_to_run.executable)\n    runfiles.extend(ctx.attr.target.default_runfiles.files.to_list())\n\n    variables = \"PYTHON_RUNFILES=\\\"${RUNFILES}\\\" \"\n    ctx.actions.expand_template(\n        template = ctx.file._sh_tpl,\n        substitutions = {\n            \"%{gcr_registry}\": ctx.attr.gcr_registry,\n            \"%{command}\": variables + _get_runfile_path(ctx, ctx.attr.target.files_to_run.executable),\n        },\n        output = ctx.outputs.executable,\n        is_executable = True,\n    )\n\n    return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))]\n\ncache_gcr_credentials = rule(\n    attrs = {\n        \"target\": attr.label(\n            mandatory = True,\n        ),\n        \"gcr_registry\": attr.string(\n            default = \"gcr.io\",\n            doc = \"If set, credentials for this GCR registry's domain will be precached\",\n        ),\n        \"_sh_tpl\": attr.label(\n            default = Label(\"//bazel/build_rules/app_chart:cache_gcr_credentials.sh.tpl\"),\n            allow_single_file = True,\n        ),\n    },\n    executable = True,\n    implementation = _impl,\n)\n\"\"\"Cache gcr credentials before running a command.\n\nThis rule executes docker-credential-gcloud before running the command, and\nreplaces the binary with a helper that is safe for concurrent execution. Works\naround around https://github.com/bazelbuild/rules_docker/issues/511.\n\nArgs:\n  target: A target that can be run with \"bazel run\".\n  gcr_registry: string. A GCR Docker registry (gcr.io/myproject).\n\"\"\"\n"
  },
  {
    "path": "bazel/build_rules/app_chart/cache_gcr_credentials.sh.tpl",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nfunction guess_runfiles() {\n    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1\n    pwd\n    popd > /dev/null 2>&1\n}\n\nRUNFILES=\"${PYTHON_RUNFILES:-$(guess_runfiles)}\"\n\n# app() uses run_parallel() to push images to GCR, which relies on\n# gcloud to get credentials. That, however, has a race condition:\n# https://github.com/google/containerregistry/issues/115\n# As such, we cache credentials and create a script that prints them to\n# replace the racy credential helper. This script is added to to the start of\n# PATH. This is harmless if run_parallel() is being used for something else.\n# It also saves ~5s of CPU time.\ntmp_bin=$(mktemp --tmpdir= -d deploy-XXXXXXXX-bin)\nexport PATH=\"${tmp_bin}:${PATH}\"\nfunction rm_tmp_bin {\n  rm -r \"${tmp_bin}\"\n}\ntrap rm_tmp_bin EXIT\n\ncredential_script=$tmp_bin/docker-credential-gcloud\ncredential_file=$tmp_bin/docker-credential-gcloud.json\ngcp_registry=$(echo \"%{gcr_registry}\" | cut -d'/' -f 1)\ndocker-credential-gcloud get <<<\"https://${gcp_registry}\" > \"${credential_file}\"\ncat > \"${credential_script}\" << EOF\n#!/bin/bash\ncat \"${credential_file}\"\nEOF\nchmod +x \"${credential_script}\"\n\n%{command} \"$@\"\n"
  },
  {
    "path": "bazel/build_rules/app_chart/push_all.bzl",
    "content": "load(\"//bazel:container_push.bzl\", \"container_push\")\n\ndef _get_runfile_path(ctx, f):\n    \"\"\"Return the runfiles relative path of f.\"\"\"\n    if ctx.workspace_name:\n        return \"${RUNFILES}/\" + ctx.workspace_name + \"/\" + f.short_path\n    else:\n        return \"${RUNFILES}/\" + f.short_path\n\ndef _impl(ctx):\n    runfiles = ctx.attr._sh_tpl.default_runfiles.files.to_list()\n    for target in ctx.attr.push_targets:\n        runfiles.append(target.files_to_run.executable)\n        runfiles.extend(target.default_runfiles.files.to_list())\n\n    ctx.actions.expand_template(\n        template = ctx.file._sh_tpl,\n        substitutions = {\n            \"%{commands}\": \"\\n\".join(\n                [\n                    \"if [[ -z \\\"${TAG:-}\\\" ]]; then echo >&2 \\\"$0: TAG environment variable must be set when pushing images.\\\"; exit 1; fi\",\n                ] + [\n                    \"async {command} --repository=\\\"${{CONTAINER_REGISTRY}}/{repository}\\\" --tag=\\\"${{TAG}}\\\"\".format(\n                        command = _get_runfile_path(ctx, target.files_to_run.executable),\n                        repository = repository,\n                    )\n                    for target, repository in zip(ctx.attr.push_targets, ctx.attr.images.keys())\n                ],\n            ),\n        },\n        output = ctx.outputs.executable,\n        is_executable = True,\n    )\n\n    return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))]\n\n_push_all = rule(\n    attrs = {\n        # Implicit dependencies.\n        \"push_targets\": attr.label_list(\n            allow_files = True,\n        ),\n        \"images\": attr.string_dict(\n            default = {},\n        ),\n        \"_sh_tpl\": attr.label(\n            default = Label(\"//bazel/build_rules/app_chart:push_all.sh.tpl\"),\n            allow_single_file = True,\n        ),\n    },\n    executable = True,\n    implementation = _impl,\n)\n\ndef push_all(name, images = {}, **kwargs):\n    \"\"\"Creates a script to push several container images to a docker registry.\n    The registry has to be specified as parameter when invoking the script.\n\n    Args:\n      images: dict. Repository names as keys and images to be pushed as values.\n    \"\"\"\n    if \"push_targets\" in kwargs:\n        fail(\"reserved for internal use by push_all macro\", attr = \"push_targets\")\n\n    images = images or {}\n    push_targets = []\n    for repository, image in images.items():\n        push_target = name + \".\" + repository + \".push\"\n        push_targets.append(push_target)\n        container_push(\n            name = push_target,\n            image = image,\n        )\n\n    _push_all(name = name, images = images, push_targets = push_targets, **kwargs)\n"
  },
  {
    "path": "bazel/build_rules/app_chart/push_all.sh.tpl",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [[ \"$#\" -lt 1 ]]; then\n  echo \"Usage: $0 <container-registry>\"\n  exit 1\nfi\nCONTAINER_REGISTRY=\"$1\"\n\nfunction guess_runfiles() {\n    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1\n    pwd\n    popd > /dev/null 2>&1\n}\n\nRUNFILES=\"${PYTHON_RUNFILES:-$(guess_runfiles)}\"\n\nPIDS=()\nfunction async() {\n    # Launch the command asynchronously and track its process id.\n    PYTHON_RUNFILES=${RUNFILES} \"$@\" &\n    PIDS+=($!)\n}\n\n%{commands}\n\nif [[ \"${#PIDS[@]}\" = 0 ]]; then\n    # It is valid to generate this script without pushing any images.\n    # Bash before v4.4 considers an empty array an unbound variable and would\n    # choke on the for-loop below.\n    exit 0\nfi\n\n# Wait for all of the subprocesses, failing the script if any of them failed.\nexitcode=0\nfor pid in \"${PIDS[@]}\"; do\n    wait ${pid} || exitcode=$?\ndone\nexit $exitcode\n"
  },
  {
    "path": "bazel/build_rules/app_chart/run_parallel.bzl",
    "content": "def _get_runfile_path(ctx, f):\n    \"\"\"Return the runfiles relative path of f.\"\"\"\n    if ctx.workspace_name:\n        return \"${RUNFILES}/\" + ctx.workspace_name + \"/\" + f.short_path\n    else:\n        return \"${RUNFILES}/\" + f.short_path\n\ndef _impl(ctx):\n    runfiles = ctx.attr._sh_tpl.default_runfiles.files.to_list()\n    for target in ctx.attr.targets:\n        runfiles.append(target.files_to_run.executable)\n        runfiles.extend(target.default_runfiles.files.to_list())\n\n    ctx.actions.expand_template(\n        template = ctx.file._sh_tpl,\n        substitutions = {\n            \"%{commands}\": \"\\n\".join([\n                \"async \\\"%s\\\" \\\"$@\\\"\" % _get_runfile_path(ctx, command.files_to_run.executable)\n                for command in ctx.attr.targets\n            ]),\n        },\n        output = ctx.outputs.executable,\n        is_executable = True,\n    )\n\n    return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))]\n\nrun_parallel = rule(\n    attrs = {\n        \"targets\": attr.label_list(\n            allow_empty = False,\n            mandatory = True,\n        ),\n        \"_sh_tpl\": attr.label(\n            default = Label(\"//bazel/build_rules/app_chart:run_parallel.sh.tpl\"),\n            allow_single_file = True,\n        ),\n    },\n    executable = True,\n    implementation = _impl,\n)\n\"\"\"Run multiple targets in parallel.\n\nThis rule builds a \"bazel run\" target that runs a series of subtargets in\nparallel. If a subtarget has errors, execution results in an error when all\nsubtargets have completed.\n\nArgs:\n  targets: A list of targets that can be run with \"bazel run\".\n\"\"\"\n"
  },
  {
    "path": "bazel/build_rules/app_chart/run_parallel.sh.tpl",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nfunction guess_runfiles() {\n    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1\n    pwd\n    popd > /dev/null 2>&1\n}\n\nRUNFILES=\"${PYTHON_RUNFILES:-$(guess_runfiles)}\"\n\nPIDS=()\nfunction async() {\n    # Launch the command asynchronously and track its process id.\n    PYTHON_RUNFILES=${RUNFILES} \"$@\" &\n    PIDS+=($!)\n}\n\n%{commands}\n\n# Wait for all of the subprocesses, failing the script if any of them failed.\nexitcode=0\nfor pid in \"${PIDS[@]}\"; do\n    wait ${pid} || exitcode=$?\ndone\nexit $exitcode\n"
  },
  {
    "path": "bazel/build_rules/app_chart/values-cloud.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\nregion: example-gcp-region\n# Token Vendor feature flags\nuse_tv_k8s_verbose: false"
  },
  {
    "path": "bazel/build_rules/app_chart/values-robot.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\n\nrobot:\n  name: \"\"\n"
  },
  {
    "path": "bazel/build_rules/copy.bzl",
    "content": "\"\"\"Macros to help rearrange files.\"\"\"\n\ndef copy_files(name, srcs, outdir, visibility = None):\n    \"\"\"Creates copies of files within a directory.\n\n    For each source file, a copy with the same basename is created in outdir. A\n    filegroup target <name> is created containing the new files.\n\n    Args:\n      srcs: list of files.\n      outdir: output directory.\n      visibility: handled through to the targets.\n    \"\"\"\n    outs = []\n    for src in srcs:\n        filename = src.split(\"/\")[-1]\n        out = \"%s/%s\" % (outdir, filename)\n        native.genrule(\n            name = \"%s_%s\" % (name, filename),\n            srcs = [src],\n            outs = [out],\n            cmd = \"cp $< $@\",\n            visibility = visibility,\n        )\n        outs.append(out)\n\n    native.filegroup(\n        name = name,\n        srcs = outs,\n        visibility = visibility,\n    )\n"
  },
  {
    "path": "bazel/build_rules/helm_chart.bzl",
    "content": "def helm_chart(ctx, name, chart, files, templates, values, version, helm, out):\n    \"\"\"Starlark function that builds a helm chart.\n\n    Args:\n      name: string. Must match the name in Chart.yaml.\n      chart: file. The Chart.yaml file.\n      files: list of non-template files to put in files/.\n      templates: list of template files.\n      values: file. The values.yaml file.\n      version: string. Overwrites any version in Chart.yaml.\n      helm: file. The Helm tool.\n      out: file. The file that the chart is built to.\n    \"\"\"\n\n    cmd = \"\"\"\n      mkdir {name} {name}/templates\n      cp {chart} {name}/Chart.yaml\n      cp {values} {name}/values.yaml\n      \"\"\".format(name = name, chart = chart.path, values = values.path)\n    if templates:\n        template_files = \" \".join([t.path for t in templates])\n\n        # Use a single cp invocation to detect filename clashes.\n        cmd += \"cp {templates} {name}/templates\\n\".format(name = name, templates = template_files)\n    if files:\n        cmd += \"mkdir {name}/files\\n\".format(name = name)\n        files_locations = \" \".join([f.path for f in files])\n\n        # Use a single cp invocation to detect filename clashes.\n        cmd += \"cp {files} {name}/files\\n\".format(name = name, files = files_locations)\n    cmd += \"\"\"\n      # Linter is too noisy, swallow its output when not failing\n      {helm} lint --strict {name} >/dev/null 2>&1 || \\\\\n        {helm} lint --strict {name}\n      {helm} package \\\\\n          --save=false --version={version} {name} \\\\\n        | (grep -v \"Successfully packaged\" || true)\n      mv $(basename {output}) {output}\n      rm -rf {name}\"\"\".format(name = name, version = version, helm = helm.path, output = out.path)\n    ctx.actions.run_shell(\n        inputs = [chart, values] + templates + (files or []),\n        tools = [helm],\n        outputs = [out],\n        command = cmd,\n        toolchain = None,\n    )\n"
  },
  {
    "path": "bazel/build_rules/helm_template.bzl",
    "content": "def helm_template(name, release_name, chart, values, namespace = None, helm_version = 2):\n    \"\"\"Locally expand a helm chart.\n\n    Args:\n      chart: build label, referencing the chart to expand.\n      values: label. File with expand-time values.\n    \"\"\"\n\n    tool = \"\"\n    cmd = \"\"\n    if helm_version == 2:\n        tool = \"@kubernetes_helm//:helm\"\n        cmd = \"$(location {tool}) template --name {name} --namespace {namespace} --values $(location {values}) $(location {chart}) > $@\".format(name = release_name, namespace = namespace or \"default\", chart = chart, tool = tool, values = values)\n    elif helm_version == 3:\n        tool = \"@kubernetes_helm3//:helm\"\n        cmd = \"$(location {tool}) template {name} $(location {chart}) --namespace {namespace} --values $(location {values}) > $@\".format(name = release_name, namespace = namespace or \"default\", chart = chart, tool = tool, values = values)\n    else:\n        fail(\"Unsupported helm version. Expected {2,3}, got \", helm_version)\n\n    native.genrule(\n        name = name,\n        srcs = [chart, values],\n        outs = [name + \".yaml\"],\n        cmd = cmd,\n        tools = [tool],\n    )\n"
  },
  {
    "path": "bazel/container_push.bzl",
    "content": "load(\"@rules_oci//oci:defs.bzl\", \"oci_push\")\n\ndef container_push(*args, **kwargs):\n    \"\"\"Creates a script to push a container image to a Docker registry. The\n    target name must be specified when invoking the push script.\"\"\"\n    if \"repository\" in kwargs:\n        fail(\n            \"Cannot set 'repository' attribute on container_push\",\n            attr = \"repository\",\n        )\n    kwargs[\"repository\"] = \"IGNORE\"\n    oci_push(*args, **kwargs)\n"
  },
  {
    "path": "bazel/debug_repository.bzl",
    "content": "\"\"\"Debug util for repository definitions.\"\"\"\n\ndef debug_repository(repo, *fields):\n    \"\"\"debug_repository(repo) identifies which version of a repository has been\n    defined in the WORKSPACE by printing some of its fields. Example:\n\n    # at the bottom of the WORKSPACE file\n    load(\"//bazel:debug_repository.bzl\", \"debug_repository\")\n\n    debug_repository(\"org_golang_x_net\")\n\n    If needed, you can override the printed fields by passing additional parameters:\n\n    debug_repository(\"io_grpc_grpc_java\", \"patches\", \"urls\")\n    \"\"\"\n\n    if len(fields) == 0:\n        fields = [\"branch\", \"commit\", \"tag\", \"url\", \"urls\"]\n\n    rule = native.existing_rule(repo)\n    if rule == None:\n        print(repo, \"not found\")\n        return\n\n    for f in fields:\n        if f in rule and len(rule[f]) > 0:\n            print(repo, f, rule[f])\n"
  },
  {
    "path": "config.sh.tmpl",
    "content": "#!/usr/bin/env bash\n\n### Required settings ###\n\n# Project ID of your Cloud Robotics GCP project. This project can be created\n# for you as part of the Terraform setup, or it can be created and configured\n# manually, then imported with `deploy.sh set-project` or `terraform import`.\nGCP_PROJECT_ID=my-project\n\n# GCP region and zone where resources should be created.\nGCP_REGION=europe-west1\nGCP_ZONE=europe-west1-c\n\n### Optional settings ###\n\n# The Docker registry all Cloud Robotics images are deployed to when installing\n# from sources. It is ignored during binary installs.\n# If unset, defaults to \"gcr.io/${GCP_PROJECT_ID}\"\n#CLOUD_ROBOTICS_CONTAINER_REGISTRY=gcr.io/my-project\n\n# A space-separated list of GCP alphanumeric project IDs for private image\n# repositories. The installer will provision GCR access to these projects,\n# both for the gke-node service account and for the robot service account.\n#PRIVATE_DOCKER_PROJECTS=\"my-project my-other-project\"\n\n# A Google Group that should be a co-owner of the created GCP project.\n#CLOUD_ROBOTICS_SHARED_OWNER_GROUP=my-group@googlegroups.com\n\n# If you want to store your Terraform state in a GCS bucket, give a bucket name\n# and a subdirectory of the bucket here. See\n# https://www.terraform.io/docs/backends/types/gcs.html for docs.\n#TERRAFORM_GCS_BUCKET=\"my-gcs-bucket\"\n#TERRAFORM_GCS_PREFIX=\"my/sub/directory\"\n\n# Symmetric cookie encryption key for the oauth2-proxy. Generate with:\n# python -c 'import os,base64; print base64.urlsafe_b64encode(os.urandom(16))'\n#CLOUD_ROBOTICS_COOKIE_SECRET=A_CyACoujODhfn2yDMy5tw==\n\n# Oauth2 client ID and client secret from\n# https://console.cloud.google.com/apis/credentials. If you leave these empty,\n# you won't be able to log in with a browser (but CLI access will work fine).\n#CLOUD_ROBOTICS_OAUTH2_CLIENT_ID=....apps.googleusercontent.com\n#CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET=...\n\n# Domain to be used for the ingress\n# If unset, defaults to \"www.endpoints.${GCP_PROJECT_ID}.cloud.goog\"\n#CLOUD_ROBOTICS_DOMAIN=www.example.com\n\n# Enable google cloud robotics layer 2\nAPP_MANAGEMENT=true\n\n# Enable google cloud robotics layer 1\nONPREM_FEDERATION=true\n\n# Disable the secret manager integration by default\nGKE_SECRET_MANAGER_PLUGIN=false\n"
  },
  {
    "path": "current_versions.txt",
    "content": "{\n  \"cert-manager\": \"1.16.3\",\n  \"ingress-nginx\": \"1.8.4\",\n  \"oauth2-proxy\": \"7.5.1\",\n  \"stackdriver-logging-agent\": \"1.9.5\"\n}\n"
  },
  {
    "path": "deploy.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Manage a deployment\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/scripts/common.sh\"\nsource \"${DIR}/scripts/config.sh\"\nsource \"${DIR}/scripts/include-config.sh\"\n\nset -o pipefail -o errexit\n\nPROJECT_NAME=\"cloud-robotics\"\nRUNFILES_ROOT=\"_main\"\n\nif is_source_install; then\n  # Not using bazel run to not clobber the bazel-bin dir\n  TERRAFORM=\"${DIR}/bazel-out/../../../external/+non_module_deps+hashicorp_terraform/terraform\"\n  HELM_COMMAND=\"${DIR}/bazel-out/../../../external/+non_module_deps+kubernetes_helm/helm\"\n  SYNK_COMMAND=\"${DIR}/bazel-bin/src/go/cmd/synk/synk_/synk\"\nelse\n  TERRAFORM=\"${DIR}/bin/terraform\"\n  HELM_COMMAND=\"${DIR}/bin/helm\"\n  SYNK_COMMAND=\"${DIR}/bin/synk\"\nfi\n\nTERRAFORM_DIR=\"${DIR}/src/bootstrap/cloud/terraform\"\nTERRAFORM_APPLY_FLAGS=${TERRAFORM_APPLY_FLAGS:- -auto-approve}\n# utility functions\n\nfunction include_config_and_defaults {\n  include_config \"$1\"\n\n  CLOUD_ROBOTICS_DOMAIN=${CLOUD_ROBOTICS_DOMAIN:-\"www.endpoints.${GCP_PROJECT_ID}.cloud.goog\"}\n  APP_MANAGEMENT=${APP_MANAGEMENT:-false}\n  ONPREM_FEDERATION=${ONPREM_FEDERATION:-true}\n  GKE_SECRET_MANAGER_PLUGIN=${GKE_SECRET_MANAGER_PLUGIN:-false}\n\n  # lets-encrypt is used as the default certificate provider for backwards compatibility purposes\n  CLOUD_ROBOTICS_CERTIFICATE_PROVIDER=${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER:-lets-encrypt}\n  CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME=${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME:-GCP_PROJECT_ID}\n  CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION=${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION:-GCP_PROJECT_ID}\n\n  CLOUD_ROBOTICS_OWNER_EMAIL=${CLOUD_ROBOTICS_OWNER_EMAIL:-$(gcloud config get-value account)}\n  CLOUD_ROBOTICS_CTX=${CLOUD_ROBOTICS_CTX:-\"gke_${GCP_PROJECT_ID}_${GCP_ZONE}_${PROJECT_NAME}\"}\n}\n\nfunction update_config_var {\n  cloud_bucket=\"gs://${1}-cloud-robotics-config\"\n  name=\"${2}\"\n  value=\"${3}\"\n\n  config_file=\"$(mktemp)\"\n  gcloud storage cp \"${cloud_bucket}/config.sh\" \"${config_file}\" 2>/dev/null || return\n\n  save_variable \"${config_file}\" \"${name}\" \"${value}\"\n\n  gcloud storage mv \"${config_file}\" \"${cloud_bucket}/config.sh\"\n}\n\nfunction prepare_source_install {\n  # For whatever reasons different combinations of bazel environemnt seem to\n  # work differently wrt bazel-bin. This hack ensure that both synk and the\n  # files that synk will install are in bazel-bin\n  tmpdir=\"$(mktemp -d)\"\n  bazel ${BAZEL_FLAGS} build //src/go/cmd/synk\n  cp -a ${DIR}/bazel-bin/src/go/cmd/synk/synk_/synk ${tmpdir}/synk\n\n  bazel ${BAZEL_FLAGS} build \\\n      \"@hashicorp_terraform//:terraform\" \\\n      \"@kubernetes_helm//:helm\" \\\n      //src/app_charts/base:base-cloud \\\n      //src/app_charts/platform-apps:platform-apps-cloud \\\n      //src/app_charts:push \\\n      //src/bootstrap/cloud:setup-robot.digest \\\n      //src/go/cmd/setup-robot:setup-robot.push\n\n  mkdir -p ${DIR}/bazel-bin/src/go/cmd/synk/synk_/\n  mv -n ${tmpdir}/synk ${DIR}/bazel-bin/src/go/cmd/synk/synk_/synk\n  rm -f ${tmpdir}/synk\n  rmdir ${tmpdir} || /bin/true\n\n  # TODO(rodrigoq): the artifactregistry API would be enabled by Terraform, but\n  # that doesn't run until later, as it needs the digest of the setup-robot\n  # image. Consider splitting prepare_source_install into source_install_build\n  # and source_install_push and using Terraform to enable the API in between.\n  gcloud services enable artifactregistry.googleapis.com \\\n    --project \"${GCP_PROJECT_ID}\"\n\n  # `setup-robot.push` is the first container push to avoid a GCR bug with parallel pushes on newly\n  # created projects (see b/123625511).\n  local oldPwd\n  oldPwd=$(pwd)\n  cd ${DIR}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh.runfiles/${RUNFILES_ROOT}\n  ${DIR}/bazel-bin/src/go/cmd/setup-robot/push_setup-robot.push.sh \\\n    --repository=\"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}/setup-robot\" \\\n    --tag=\"latest\"\n\n  # The tag variable must be called 'TAG', see cloud-robotics/bazel/container_push.bzl\n  # Running :push outside the build system shaves ~3 seconds off an incremental build.\n  cd ${DIR}/bazel-bin/src/app_charts/push.runfiles/${RUNFILES_ROOT}\n  TAG=\"latest\" ${DIR}/bazel-bin/src/app_charts/push \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\"\n  cd ${oldPwd}\n}\n\nfunction terraform_exec {\n  ( cd \"${TERRAFORM_DIR}\" && ${TERRAFORM} \"$@\" )\n}\n\nfunction terraform_init {\n  local ROBOT_IMAGE_DIGEST\n  ROBOT_IMAGE_DIGEST=$(cat bazel-bin/src/bootstrap/cloud/setup-robot.digest)\n\n  # We only need to create dns resources if a custom domain is used.\n  local CUSTOM_DOMAIN\n  if [[ \"${CLOUD_ROBOTICS_DOMAIN}\" != \"www.endpoints.${GCP_PROJECT_ID}.cloud.goog\" ]]; then\n    CUSTOM_DOMAIN=\"${CLOUD_ROBOTICS_DOMAIN}\"\n  fi\n\n  # This variable is set by src/bootstrap/cloud/run-install.sh for binary installs\n  local CRC_VERSION\n  if [[ -z \"${TARGET}\" ]]; then\n    # TODO(ensonic): keep this in sync with the nightly release script\n    VERSION=${VERSION:-\"0.1.0\"}\n    if [[ -d .git ]]; then\n      SHA=$(git rev-parse --short HEAD)\n    else\n      echo \"WARNING: no git dir and no \\$TARGET env set\"\n      SHA=\"unknown\"\n    fi\n    CRC_VERSION=\"crc-${VERSION}/crc-${VERSION}+${SHA}\"\n  else\n    CRC_VERSION=\"${TARGET%.tar.gz}\"\n  fi\n\n  cat > \"${TERRAFORM_DIR}/terraform.tfvars\" <<EOF\n# autogenerated by deploy.sh, do not edit!\nname = \"${GCP_PROJECT_ID}\"\nid = \"${GCP_PROJECT_ID}\"\ndomain = \"${CUSTOM_DOMAIN}\"\nzone = \"${GCP_ZONE}\"\nregion = \"${GCP_REGION}\"\nshared_owner_group = \"${CLOUD_ROBOTICS_SHARED_OWNER_GROUP}\"\nrobot_image_reference = \"${SOURCE_CONTAINER_REGISTRY}/setup-robot@${ROBOT_IMAGE_DIGEST}\"\ncrc_version = \"${CRC_VERSION}\"\ncertificate_provider = \"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}\"\ncluster_type = \"${GKE_CLUSTER_TYPE}\"\ndatapath_provider = \"${GKE_DATAPATH_PROVIDER}\"\nonprem_federation = ${ONPREM_FEDERATION}\nsecret_manager_plugin = ${GKE_SECRET_MANAGER_PLUGIN}\nEOF\n\n# Add certificate information if the configured provider requires it\n  if [[ ! \"none self-signed\" =~ (\" \"|^)\"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}\"(\" \"|$) ]]; then\n    cat >> \"${TERRAFORM_DIR}/terraform.tfvars\" <<EOF\ncertificate_subject_common_name = \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME}\"\ncertificate_subject_organization = \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION}\"\nEOF\n\n  if [[ -n \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}\" ]]; then\n      cat >> \"${TERRAFORM_DIR}/terraform.tfvars\" <<EOF\ncertificate_subject_organizational_unit = \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}\"\nEOF\n    fi\n  fi\n\n  echo 'additional_regions = {' >> \"${TERRAFORM_DIR}/terraform.tfvars\"\n  local AR\n  for AR in \"${ADDITIONAL_REGIONS[@]}\"; do\n    local AR_NAME\n    local AR_REGION\n    local AR_ZONE\n\n    AR_NAME=$(jq -r .name <<<\"${AR}\")\n    AR_REGION=$(jq -r .region <<<\"${AR}\")\n    AR_ZONE=$(jq -r .zone <<<\"${AR}\")\n\n    cat >> \"${TERRAFORM_DIR}/terraform.tfvars\" <<EOF\n    ${AR_NAME} = { region: \"${AR_REGION}\", zone: \"${AR_ZONE}\" },\nEOF\n  done\n  echo '}' >> \"${TERRAFORM_DIR}/terraform.tfvars\"\n\n# Docker private projects\n\n  if [[ -n \"${PRIVATE_DOCKER_PROJECTS:-}\" ]]; then\n    cat >> \"${TERRAFORM_DIR}/terraform.tfvars\" <<EOF\nprivate_image_repositories = [\"${PRIVATE_DOCKER_PROJECTS// /\\\", \\\"}\"]\nEOF\n  fi\n\n# Terraform bucket\n\n  if [[ -n \"${TERRAFORM_GCS_BUCKET:-}\" ]]; then\n    cat > \"${TERRAFORM_DIR}/backend.tf\" <<EOF\n# autogenerated by deploy.sh, do not edit!\nterraform {\n  backend \"gcs\" {\n    bucket = \"${TERRAFORM_GCS_BUCKET}\"\n    prefix = \"${TERRAFORM_GCS_PREFIX}\"\n  }\n}\nEOF\n  else\n    rm -f \"${TERRAFORM_DIR}/backend.tf\"\n  fi\n\n  terraform_exec init -upgrade -reconfigure \\\n    || die \"terraform init failed\"\n}\n\nfunction terraform_apply {\n  terraform_init\n\n  terraform_exec apply ${TERRAFORM_APPLY_FLAGS} \\\n    || die \"terraform apply failed\"\n  terraform_post\n}\n\nfunction terraform_post {\n  local OLD_CLOUD_ROBOTICS_CTX\n  local location\n\n  OLD_CLOUD_ROBOTICS_CTX=\"${CLOUD_ROBOTICS_CTX}\"\n  CLOUD_ROBOTICS_CTX=$(gke_context_name \"${GCP_PROJECT_ID}\" \"cloud-robotics\" \"${GCP_REGION}\" \"${GCP_ZONE}\")\n  [[ -z \"${CLOUD_ROBOTICS_CTX}\" ]] && die \"no cloud-robotics cluster found\"\n  if [[ \"${OLD_CLOUD_ROBOTICS_CTX}\" != \"${CLOUD_ROBOTICS_CTX}\" ]]; then\n    echo \"updating CLOUD_ROBOTICS_CTX from ${OLD_CLOUD_ROBOTICS_CTX} to ${CLOUD_ROBOTICS_CTX}\"\n    update_config_var ${GCP_PROJECT_ID} \"CLOUD_ROBOTICS_CTX\" \"${CLOUD_ROBOTICS_CTX}\"\n  fi\n}\n\nfunction terraform_delete {\n  terraform_init\n  terraform_exec destroy -auto-approve || die \"terraform destroy failed\"\n}\n\nfunction helm_region_shared {\n  local CLUSTER_CONTEXT\n  local CLUSTER_DOMAIN\n  local INGRESS_IP\n  local CLUSTER_REGION\n  local CLUSTER_ZONE\n  local CLUSTER_NAME\n\n  CLUSTER_CONTEXT=\"${1}\"\n  CLUSTER_DOMAIN=\"${2}\"\n  INGRESS_IP=\"${3}\"\n  CLUSTER_REGION=\"${4}\"\n  CLUSTER_ZONE=\"${5}\"\n  CLUSTER_NAME=\"${6}\"\n\n  gke_get_credentials \"${GCP_PROJECT_ID}\" \"${CLUSTER_NAME}\" \"${CLUSTER_REGION}\" \"${CLUSTER_ZONE}\"\n\n  # Wait for the GKE cluster to be reachable.\n  i=0\n  until kubectl --context \"${CLUSTER_CONTEXT}\" get serviceaccount default &>/dev/null; do\n    sleep 1\n    i=$((i + 1))\n    if ((i >= 60)) ; then\n      # Try again, without suppressing stderr this time.\n      if ! kubectl --context \"${CLUSTER_CONTEXT}\" get serviceaccount default >/dev/null; then\n        die \"'kubectl get serviceaccount default' failed\"\n      fi\n    fi\n  done\n\n  local BASE_NAMESPACE\n  BASE_NAMESPACE=\"default\"\n\n  # Remove old unmanaged cert\n  if ! kubectl --context \"${CLUSTER_CONTEXT}\" get secrets cluster-authority -o yaml | grep -q \"cert-manager.io/certificate-name: selfsigned-ca\"; then\n    kubectl --context \"${CLUSTER_CONTEXT}\" delete secrets cluster-authority 2> /dev/null || true\n  fi\n\n  # Delete permissive binding if it exists from previous deployments\n  if kubectl --context \"${CLUSTER_CONTEXT}\" get clusterrolebinding permissive-binding &>/dev/null; then\n    kubectl --context \"${CLUSTER_CONTEXT}\" delete clusterrolebinding permissive-binding\n  fi\n\n\n  local values\n  values=(\n    --set-string \"domain=${CLUSTER_DOMAIN}\"\n    --set-string \"ingress_ip=${INGRESS_IP}\"\n    --set-string \"project=${GCP_PROJECT_ID}\"\n    --set-string \"region=${CLUSTER_REGION}\"\n    --set-string \"registry=${SOURCE_CONTAINER_REGISTRY}\"\n    --set-string \"owner_email=${CLOUD_ROBOTICS_OWNER_EMAIL}\"\n    --set-string \"app_management=${APP_MANAGEMENT}\"\n    --set-string \"onprem_federation=${ONPREM_FEDERATION}\"\n    --set-string \"certificate_provider=${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}\"\n    --set-string \"deploy_environment=${CLOUD_ROBOTICS_DEPLOY_ENVIRONMENT}\"\n    --set-string \"oauth2_proxy.client_id=${CLOUD_ROBOTICS_OAUTH2_CLIENT_ID}\"\n    --set-string \"oauth2_proxy.client_secret=${CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET}\"\n    --set-string \"oauth2_proxy.cookie_secret=${CLOUD_ROBOTICS_COOKIE_SECRET}\"\n    --set \"use_tv_verbose=${CRC_USE_TV_VERBOSE}\"\n  )\n\n  ${SYNK_COMMAND} --context \"${CLUSTER_CONTEXT}\" init\n  echo \"synk init done\"\n\n  echo \"installing base-cloud to ${CLUSTER_CONTEXT}...\"\n  ${HELM_COMMAND} --kube-context \"${CLUSTER_CONTEXT}\" template -n base-cloud --namespace=${BASE_NAMESPACE} \"${values[@]}\" \\\n      ./bazel-bin/src/app_charts/base/base-cloud-0.0.1.tgz \\\n    | ${SYNK_COMMAND} --context \"${CLUSTER_CONTEXT}\" apply base-cloud -n ${BASE_NAMESPACE} -f - \\\n    || die \"Synk failed for base-cloud\"\n\n  # This is the main region. Only run this here!\n  if [[ \"${CLUSTER_NAME}\" = \"${PROJECT_NAME}\" ]]; then\n    echo \"installing platform-apps-cloud to ${CLOUD_ROBOTICS_CTX}...\"\n    ${HELM_COMMAND} --kube-context \"${CLUSTER_CONTEXT}\" template -n platform-apps-cloud \"${values[@]}\" \\\n        ./bazel-bin/src/app_charts/platform-apps/platform-apps-cloud-0.0.1.tgz \\\n      | ${SYNK_COMMAND} --context \"${CLUSTER_CONTEXT}\" apply platform-apps-cloud -f - \\\n      || die \"Synk failed for platform-apps-cloud\"\n  fi\n}\n\nfunction helm_main_region {\n  local INGRESS_IP\n  INGRESS_IP=$(terraform_exec output ingress-ip | tr -d '\"')\n\n  helm_region_shared \\\n    \"${CLOUD_ROBOTICS_CTX}\" \\\n    \"${CLOUD_ROBOTICS_DOMAIN}\" \\\n    \"${INGRESS_IP}\" \\\n    \"${GCP_REGION}\" \\\n    \"${GCP_ZONE}\" \\\n    \"${PROJECT_NAME}\"\n}\n\nfunction helm_additional_region {\n  local ar_description\n  ar_description=\"${1}\"\n\n  local AR_NAME\n  local AR_REGION\n  local AR_ZONE\n\n  AR_NAME=$(jq -r .name <<<\"${ar_description}\")\n  AR_REGION=$(jq -r .region <<<\"${ar_description}\")\n  AR_ZONE=$(jq -r .zone <<<\"${ar_description}\")\n\n  local CLUSTER_NAME\n  CLUSTER_NAME=\"${AR_NAME}-ar-cloud-robotics\"\n\n  local INGRESS_IP\n  INGRESS_IP=$(terraform_exec output -json ingress-ip-ar | jq -r .\"\\\"${CLUSTER_NAME}\\\"\")\n\n  helm_region_shared \\\n    $(gke_context_name \"${GCP_PROJECT_ID}\" \"${CLUSTER_NAME}\" \"${AR_REGION}\" \"${AR_ZONE}\") \\\n    \"${AR_NAME}.${CLOUD_ROBOTICS_DOMAIN}\" \\\n    \"${INGRESS_IP}\" \\\n    \"${AR_REGION}\" \\\n    \"${AR_ZONE}\" \\\n    \"${CLUSTER_NAME}\"\n}\n\nfunction helm_charts {\n  helm_main_region\n\n  local AR\n  for AR in \"${ADDITIONAL_REGIONS[@]}\"; do\n    helm_additional_region \"${AR}\"\n  done\n}\n\n# commands\n\nfunction set_config {\n  local project_id=\"$1\"\n  ${DIR}/scripts/set-config.sh \"${project_id}\"\n}\n\nfunction create {\n  include_config_and_defaults $1\n  if is_source_install; then\n    prepare_source_install\n  fi\n  terraform_apply\n  helm_charts\n}\n\nfunction delete {\n  include_config_and_defaults $1\n  if is_source_install; then\n    bazel ${BAZEL_FLAGS} build \"@hashicorp_terraform//:terraform\"\n  fi\n  terraform_delete\n}\n\n# Alias for create.\nfunction update {\n  create $1\n}\n\n# This is a shortcut for skipping Terraform config checks if you know the config has not changed.\nfunction fast_push {\n  include_config_and_defaults $1\n  if is_source_install; then\n    prepare_source_install\n  fi\n  helm_charts\n}\n\n# This is a shortcut for skipping building and applying Terraform configs if you know the build has not changed.\nfunction update_infra {\n  include_config_and_defaults $1\n  terraform_apply\n}\n\n# main\nif [[ \"$#\" -lt 2 ]] || [[ ! \"$1\" =~ ^(set_config|create|delete|update|fast_push|update_infra)$ ]]; then\n  die \"Usage: $0 {set_config|create|delete|update|fast_push|update_infra} <project id>\"\nfi\n\n# log and call arguments verbatim:\nlog $2 $0 $1\n\"$@\"\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "# Files created when running Jekyll locally, following\n# https://help.github.com/en/articles/setting-up-your-github-pages-site-locally-with-jekyll\n.sass-cache/\n_site/\nGemfile\nGemfile.lock\n"
  },
  {
    "path": "docs/_config.yml",
    "content": "theme: jekyll-theme-slate\n"
  },
  {
    "path": "docs/concepts/app-management.md",
    "content": "# App Management\n\nThe Cloud Robotics Core application management (Layer 2) makes it easy to define and deploy\narbitrary applications across a fleet of cloud and robot clusters. The\n[Helm v2 chart format](https://helm.sh/docs/developing_charts/) is used to define an application\nat the scope of a single cluster. Additional custom resources tie them together to a\ncross-cluster application and define their deployment. It relies on the Cloud Robotics Core\n[federation layer](federation.md) to distribute the resources to the right clusters.\n\n## App resource\n\nThe App resource defines a Cloud Robotics Core application by simply describing Helm charts for\ntwo classes of clusters: cloud and robots. Charts may be specified inline as base64-encoded Helm\nchart files or by referencing a Helm repository. App resources will be deployed to the cloud\ncluster.\n\nExample 1: application referencing charts from helm repositories\n\n```yaml\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: App\nmetadata:\n  name: ros-v1\nspec:\n  repository: https://my.repo\n  version: 1.2.1\n  components:\n    cloud:\n      name: ros-cloud\n    robot:\n      name: ros-robot\n```\n\nExample 2: application using inline as base64-encoded charts (development workflow)\n\n```yaml\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: App\nmetadata:\n  name: ros-v1\nspec:\n  components:\n    cloud:\n      chart:\n        inline: <base64>\n    robot:\n      chart:\n        inline: <base64>\n```\n\nRight now we only have bazel build rules to produce inline charts.\n\n## AppRollout Resource\n\nAn AppRollout describes how a defined App should be deployed across a fleet of clusters. It allows\nto flexibly select robots which should run an application and to inject fine-grained configuration.\n\nExample 3: AppRollout with different configuration options per target\n\n```yaml\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: AppRollout\nmetadata:\n  name: ros-stable\n  labels:\n    app.kubernetes.io/name: ros\n    role: navtest\n    release: stable\nspec:\n  appName: ros-v1\n  cloud:\n    values:\n      override: foo\n  robots:\n  - selector:\n      matchLabels:\n        model: mir100\n    values:\n      override1: bar\n  - selector:\n      matchLabels:\n        model: mir200\n    values:\n      override1: baz\n    version: v1.2.2   # Chart version override for canarying\n```\n\nAppRollouts are deployed into the cloud cluster, where a controller (app-rollout-controller) handles them.\nThe controller applies the specified selectors and creates or updates the internal\nChartAssignments. A ChartAssignment represents a single instance of a chart that should be\ninstalled into a single cluster. These internal objects describe the task of installing parts of\nthe application.\n\nThe federation layer will sync ChartAssignments to robots as needed. The actual\ninstallation is done by another controller (chart-assignment-controller), this\ntime running both in the cloud and on the robots. The AppRollout controller will\nwatch the status updates and consolidate the information into status updates on\nthe AppRollout.\n\n## Sharing secrets\n\nIf you create a Secret in the `default` namespace labelled\n`cloudrobotics.com/copy-to-chart-namespaces=true`, it will be copied into all\nnamespaces created by the chart-assignment-controller. This is useful for\ncluster-specific license keys that can be used by applications.\n\n## Opt a pod out of status checking\n\nDuring rollout, the chart-assignment-controller checks for Pods in the rollout being `Running` or `Completed`.\nIn some cases, this check is not necessary or might need to be opted out of.\nIn this case, add a label `cloudrobotics.com/opt-out-error-checking=true` to your pods. Adding this\ninstructs the chart-assignment-controller to not block the status from reaching `Ready`.\n"
  },
  {
    "path": "docs/concepts/config.md",
    "content": "# Project configuration\n\nThe project configuration that one has entered during the initial setup is\nstored with the project in GCS. One can look at the options with the following\ncommand:\n\n```shell\ngcloud storage cat gs://${PROJECT_ID}-cloud-robotics-config/config.sh\n```\n\nThe settings contained in the config file are used by terraform to setup the\nproject infrastructure and used by the cloud and chart-assignment-controller services running\nin kubernetes to configure apps.\n\nThe terraform support is encapsulated in deploy.sh that creates a temporary\n`terraform.tfvars` file.\n\nTo support configuring apps, we pass the settings to app-rollout-controller where they are\nprovided as additional variables for helm templating. The command below prints\nthe settings we pass to app-rollout-controller:\n\n```shell\nkubectl get deployment app-rollout-controller -o=jsonpath='{.spec.template.spec.containers[0].args[0]}'\n```\n\n"
  },
  {
    "path": "docs/concepts/device_identity.md",
    "content": "# Device Identity\n\nDevice Identity, part of Layer 1, provides an identity for robot clusters and\nservices to integrate those identities into a cloud based IAM system.\n\nThe following components are part of the whole setup:\n* Cloud:\n  * `IAM`: Cloud Identity and Access Management\n  * `Kubernetes configmaps`: used as a Key Management Service\n  * `Token Vendor`: token exchange service for OAuth2 service accounts\n  * `robot service-account`: a GCP IAM service account that has the union of\n    permissions that applications running on the robot cluster require\n* Robot cluster (on-prem or edge):\n  * `Metadata Server`: provides default credentials + project metadata\n  * `Setup`: special app used to register the workcell\n  * `<App>`: any app accessing the cloud\n\nThe following chapters explain the flows in more detail. Further information\nabout the Token Vendor can be found in its\n[docs](https://github.com/googlecloudrobotics/core/tree/master/src/go/cmd/token-vendor/README.md)\n\n## Setup\n\nThe setup flow is used to register a new robot cluster to a cloud project.\n\n![setup](device_identity_setup.png)\n\n* (1) (Admin-)user runs `Setup`, which generates a RSA key-pair and stores it as\n  a K8S secret\n* (2) `Setup` uploads the public key to `Token Vendor`\n* (3) `Token Vendor` stores key in `Kubernetes`\n\n\n## Authentication\n\nThe authentication flow is used to transparently make cloud API calls work for\non-prem robot clusters.\n\n![setup](device_identity_auth.png)\n\n* `<App>` creates an API client without loading any custom key material\n* (1) API client library probes `Metadata Server` to get ADCs (Application\n  Default Credentials)\n* (2) `Metadata Server` talks to `Token Vendor` get an Access Token for the\n  `robot service-account`\n* (3) `Token Vendor` verifies the key the request has been signed with against\n  the device registry\n* (4) `Token Vendor` gets an Access Token for the `robot service-account` from\n  `IAM`\n* `Token Vendor` returns Access Token through `Metadata Server` to the\n  `<App>` and that can use it to call Cloud APIs under the scope of the\n  `robot service-account`\n\n"
  },
  {
    "path": "docs/concepts/federation.md",
    "content": "# Federation\n\nFederation, part of Layer 1, is responsible for synchronizing the state between robot and cloud\nclusters. Configuration state in Cloud Robotics Core is primarily expressed through [custom\nresources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)\nby platform and user applications alike. Our federation system enables other components to use\ncustom resources locally without needing to be aware of the multi cluster setup and the quality\nof the network connection.\n\n## Semantics\nA Kubernetes resource is typically divided into a [“spec” and a “status”](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#object-spec-and-status)\nsection. The `spec` section expresses the intent of the resource, typically authored by a user\nor another application along with all its metadata.\nThe `status` section must generally only be written to by the controller that is responsible for\nrealizing the specification. Consequently, `spec` and `status` typically each have one distinct\nauthor.\n\nFor federating resources, this means that `spec` and `status` of a resource are owned by at most\none cluster respectively (possibly the same one). The cluster owning the spec is also the main\nowner of the resource overall and controls its lifecycle, i.e. deletion.\nA resource’s spec is always synced from an upstream cluster to a downstream\ncluster and its status synced back from downstream to upstream.\n\nAll resources of a specific type may either be synchronized to all robots or to exactly one robot.\nThere is no direct synchronization between robots. However, a robot may create a resource in the\ncloud cluster that will be distributed to other robots.\n\nIf a resource owned by the upstream cluster has been synchronized to one or more\ndownstream clusters, it can only be permanently deleted upstream: if deleted\ndownstream, it will be recreated. If deleted in the upstream cluster, it will be\nasynchronously deleted in other clusters that hold a copy of the resource.\nUpstream deletion can complete before the downstream resource is deleted.\n\n## cr-syncer\nThe cr-syncer component (Custom Resource Syncer) is a controller that runs inside each robot\ncluster. It is connected to the Kubernetes API servers of the cloud and the robot cluster alike\nand continuously watches for updates on custom resources. The controller contains retry and resync\nlogic to address intermittent connectivity.\n\n![federation](federation.png)\n\nThe behavior of the cr-syncer can be configured per custom resource definition (CRD) by setting\nannotations on its CRD:\n\n* `cr-syncer.cloudrobotics.com/spec-source`: may be `cloud` or `robot`. It determines which\n  cluster type owns metadata, spec, and lifecycle of all resources of the CRD. It implies\n  that the other cluster type owns the status section.\n* `cr-syncer.cloudrobotics.com/filter-by-robot-name`: a boolean that determines whether resources\n  will be synced to all robots or just a single one. An individual resource is labeled with\n  `cloudrobotics.com/robot-name` to indicate which robot it should be synced to. If the label\n  is missing on a resource, it will not be synced at all.\n* `cr-syncer.cloudrobotics.com/status-subtree`: a string key, which defines which sub-section of\n  the resource status is synced from the downstream cluster. This lets you split\n  a resource’s status into `robot` and `cloud` sections, for example. Using this\n  annotation is generally discouraged as it likely points to a flaw in the\n  modeling of the respective CRD.\n\n## Deletion\nWhen the cr-syncer sees a resource in the downstream cluster with no\ncorresponding resource in upstream cluster, it deletes it. This handles orphaned\nresources when the upstream resource was deleted while the cr-syncer was\nrestarting. It also means that you can't create a resource directly in the\ndownstream cluster. The upstream resource is identified using the namespace and\nname, but not the UID, so deletion and recreation upstream may result in an\nupdate in the downstream cluster.\n\n> **Note**: if you create a resource directly in the downstream cluster, the\n> behavior will depend on how the CRD is annotated.  If `filter-by-robot-name`\n> is false, the cr-syncer will delete all downstream resources that don't\n> correspond to upstream resources. This means that by listing CRs in the\n> upstream cluster, you can reason about which CRs will exist in the downstream\n> cluster.\n>\n> If `cr-syncer.cloudrobotics.com/filter-by-robot-name` is true, then the\n> cr-syncer will ignore any downstream resources that are not labelled with a\n> matching robot name. This means that a robot can run ChartAssignments that are\n> synced from the cloud as well as those created directly in the robot cluster.\n\nIn some cases, downstream deletion may be blocked. For example, if we have\ndeleted an upstream ChartAssignment, but the chart-assignment-controller has failed to remove\nits finalizer from the downstream ChartAssignment. This edge case leads to\nsurprising behavior:\n\n- An upstream ChartAssignment can be recreated before the downstream\n  ChartAssignment is deleted.\n- The old status from the downstream cluster will be synced to the new upstream\n  ChartAssignment.\n\nIf needed, this can be detected by watching the downstream cluster after\ndeleting the resource from the downstream cluster. The situation will clean up\nonce downstream deletion is complete.\n\nNote: previously, the cr-syncer used finalizers to block upstream deletion\nuntil the downstream resource was deleted. This gave the original deleter more\ninformation: for example, once an AppRollout had been deleted in the cloud, it\nmeans that all robots have terminated the app's pods. However, this caused\nproblems with offline or renamed clusters: an admin would have to manually clean\nup the old finalizers. The new asynchronous behavior is not affected by offline\nclusters.\n\n## Resource generations\n\nCustom resources have a field `.metadata.generation` that starts at 1 and is\nincremented when the resource changes. Specifically, if the CRD enables the\n/status subresource, the generation increases by 1 every time the resource spec\nchanges, but not when the status changes. The resource controller can set\n`.status.observedGeneration` to the latest generation it has observed, so the\nuser can change the spec, then wait for `observedGeneration` to catch up before\nlooking at the status. For example:\n\n* Create a Deployment for one pod (generation=1), and wait for the status to be Ready.\n* Change the Deployment's image reference (generation=2): the status is still\n  Ready, but this refers the old spec (observedGeneration=1).\n* Wait for the status to update (observedGeneration=2): now the status is\n  non-ready, referring to the newer spec.\n* Wait for the status to be Ready. The new image is now running.\n\n`generation` and `observedGeneration` can **only be compared in the downstream\ncluster**. As the generation is managed by the Kubernetes apiserver, the\ncr-syncer cannot guarantee that the upstream generation matches the downstream\ngeneration. On the other hand, `observedGeneration` will be copied from\ndownstream to upstream with the rest of `.status`. This means that `generation`\nis cluster-specific but `observedGeneration` always refers to the downstream\ngeneration.\n"
  },
  {
    "path": "docs/developers/debug-auth.md",
    "content": "# Debugging authentication problems\n\nUseful tips for working with Authentication and Authorization systems.\n\n## Run a sample request with various credentials\n\nYou can call Cloud APIs with curl to see whether authorization works.\n\n### Your own credentials\n\n```bash\nPROJECT_NUMBER=201199916163\ncurl -v -H \"Content-Type: application/json\" \\\n        -H \"Authorization: Bearer $(gcloud auth application-default print-access-token)\" \\\n        \"https://cloudroboticssensordata.googleapis.com/v1eap/projects/${PROJECT_NUMBER}/sensors\"\n```\n\n### Service account JSON file\n\nYou can create a JSON file with the robot account's credentials on\nthe [Cloud console's credentials page](https://console.cloud.google.com/apis/credentials).\n\n```bash\nPROJECT_NUMBER=201199916163\nJSON_CREDENTIALS=/tmp/my-project-b7364a68fa92.json\ncurl -v -H \"Content-Type: application/json\" \\\n        -H \"Authorization: Bearer $(GOOGLE_APPLICATION_CREDENTIALS=${JSON_CREDENTIALS} gcloud auth application-default print-access-token)\" \\\n        \"https://cloudroboticssensordata.googleapis.com/v1eap/projects/${PROJECT_NUMBER}/sensors\"\n```\n\n### Get an OAuth token from IAM\n\nThe token vendor doesn't have its own keys, but instead calls IAM's\ngenerateAccessToken method. You can emulate its behavior by using the [API\nExplorer](https://developers.google.com/apis-explorer/#search/iam%20credentials/iamcredentials/v1/iamcredentials.projects.serviceAccounts.generateAccessToken)\nto call `iamcredentials.projects.serviceAccounts.generateAccessToken`. The name\nparameter is\n`projects/-/serviceAccounts/robot-service@my-project.iam.gserviceaccount.com`,\nand the `scope` is `https://www.googleapis.com/auth/cloud-platform`.\n\nYou can pass the returned access token in an Authorization header as above.\n\n## Check whether the client's request is well-formed and authenticated\n\nThe easiest way to verify that metadata server and the gRPC client library\nare doing the right thing is to use a logging HTTP server as the gRPC server.\nInstead of setting the gRPC host to the Cloud API server\n(`cloudroboticssensordata.googleapis.com`), you set it to an\nHTTPS-capable server under your control. You need HTTPS support because\notherwise the gRPC library will rightfully decline to send access tokens.\n\nLuckily, your Cloud Robotics Core setup already runs an HTTPS server. Suppose\nyou're calling the gRPC service\n`google.cloud.robotics.sensordata.v1eap.SensorDataService`. You can\nhook a very simple Python HTTP server into your cloud nginx setup like\nthis:\n\n```yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: debug\nspec:\n  server.py: |\n    import BaseHTTPServer\n    import SocketServer\n\n    class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):\n\n      def do_GET(self):\n        print \"Got request for \", self.path, \" with auth \", self.headers.get('Authorization')\n\n      def do_POST(self):\n        print \"Got request for \", self.path, \" with auth \", self.headers.get('Authorization')\n\n    httpd = SocketServer.TCPServer((\"\", 8080), MyHandler)\n    httpd.serve_forever()\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: debug\nspec:\n  selector:\n    matchLabels:\n      app: debug\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: debug\n    spec:\n      containers:\n      - name: python\n        image: python:2\n        args: [\"python\", \"/src/server.py\"]\n        volumeMounts:\n        - name: src-volume\n          mountPath: /src\n      volumes:\n      - name: src-volume\n        configMap:\n          name: debug\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: debug\n  name: debug\nspec:\n  ports:\n  - name: http\n    port: 8082\n    protocol: TCP\n    targetPort: 8080\n  selector:\n    app: debug\n  type: ClusterIP\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  annotations:\n    nginx.ingress.kubernetes.io/backend-protocol: HTTP\n  name: debug\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: www.endpoints.my-project.cloud.goog\n    http:\n      paths:\n      - path: /google.cloud.robotics.sensordata.v1eap.SensorDataService\n        pathType: Prefix\n        backend:\n          service:\n            name: debug\n            port\n              number: 8080\n```\n\nThis will log the Authorization header to the pod's stdout, so you can view it\nwith `kubectl logs`. Save the token to a file (don't paste it into the command\nline because it will end up in your shell history).\n\n## Checking tokens with the tokeninfo service\n\nYou can check the token's contents and sanity with Google's tokeninfo endpoint:\n\n```shell\ncurl https://oauth2.googleapis.com/tokeninfo?access_token=$(cat /tmp/token.txt)\n```\n"
  },
  {
    "path": "docs/how-to/connecting-robot.md",
    "content": "# Connecting a robot to the cloud\n\nEstimated time: 10 min\n\nThis page describes how to connect a Kubernetes cluster on a robot running Ubuntu 20.04 to the cloud.\n\nOnce you've done this, you can:\n\n* Run a private Docker container from the Google Container Registry\n* Securely communicate with cloud services\n* See logs from the robot in the Cloud Console\n\n## Setting up the GCP project\n\n1. If you haven't already, complete the [Setting up the GCP project](../quickstart.md) steps.\n\n1. On the computer you used to set up the cloud project, generate an access token, which you'll use to give the robot access to the cloud:\n\n    ```shell\n    gcloud auth application-default print-access-token\n    ```\n\n> **Note:** If you want to reduce the risk that your cloud project is\n> compromised using this token during its 1h lifetime, you can generate a less\n> privileged service account token:\n>\n> ```\n> SA=\"human-acl@${PROJECT_ID}.iam.gserviceaccount.com\"\n> gcloud iam service-accounts add-iam-policy-binding \"${SA}\" \\\n>   --role=roles/iam.serviceAccountTokenCreator \\\n>   --project=\"${PROJECT_ID}\" --member=\"user:${YOUR_EMAIL_ADDRESS:?}\"\n> gcloud auth print-access-token --impersonate-service-account=\"${SA}\"\n> ```\n>\n> If you see `ERROR: Failed to impersonate ...`, wait a few minutes for the IAM\n> policy to propagate.\n>\n> You can ignore the \"WARNING: This command is using service account\n> impersonation.\"\n\n## Installing the cluster on the robot\n\n## Installing Kubernetes\n\nYou'll need to install a Kubernetes cluster on the robot before you can connect it to the cloud. The cluster manages and supports the processes that communicate with the cloud.\n\nPlease see external references for setting up k8s. For simplicity we recommend\n[k3s](https://k3s.io/) or a single node\n[kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)\ncluster (untested).\n\n## Setting up the robot\n\n1. Set up the robot cluster to connect to the cloud. When running `setup_robot.sh`, you'll need to enter the access token you generated earlier. You may find it easiest if you SSH into the robot from the workstation you used to set up the project.\n\n    ```shell\n    mkdir -p ~/cloud-robotics-core\n    cd ~/cloud-robotics-core\n    curl https://raw.githubusercontent.com/googlecloudrobotics/core/master/src/bootstrap/robot/setup_robot.sh >setup_robot.sh\n    bash setup_robot.sh my-robot --project ${PROJECT_ID} \\\n      --robot-type my-robot-type\n    ```\n\n    Set `${PROJECT_ID}` to your GCP project ID. When prompted for an access token, provide the authentication token you generated earlier.\n\n    > **Note:** `my-robot-type` is a placeholder and you can ignore it for now.\n\n## What's next\n\n* [Using Cloud Storage from a robot](using-cloud-storage.md).\n"
  },
  {
    "path": "docs/how-to/creating-declarative-api.md",
    "content": "# Creating a declarative API\n\n<!-- Estimated time: TODO -->\n\nIn this guide we will use a Kubernetes-style declarative API to interface to an external Charge Service for a robot.\nThis API is built around the concept of a ChargeAction resource, which instructs a robot to drive to a charger.\nWhile the robot is charging, the status of the ChargeAction resource is kept up-to-date and can be observed.\n\n## Motivation\n\nRPC-based systems like ROS's [actionlib](http://wiki.ros.org/actionlib), while proven to be scalable, maintainable and useful, leave a few things to be desired:\n\n1. **Synchronization**. The intent for a controller is stored in-memory in multiple components and we rely on correct synchronization.\nFor example, the motion planner sends the \"turn wheel 3 times per second\" message to the wheel actuator, then trusts that the wheel actuator will have received the intent and waits for it to act on the shared intent.\nIf a second process (such as an emergency stop) overwrites the intent of the wheel actuator, there's no standard channel to notify the motion planner.\n\n2. **Persistence**. Since the intent is stored in-memory, it is lost when any process restarts.\nThis is the core reason that software in ROS systems can't be updated on the fly.\n\n3. **Inspection**. For debugging, a coherent view into the current system intent would be great.\nIn RPC-based APIs, the intent is often updated differentially (eg \"a little more to the left\"), so our only hope of debugging is to log all messages ever sent.\n\nIn a declarative API, all actions and feedback are stored in a shared database&mdash;an approach built on Kubernetes' experience building robust distributed systems&mdash;which addresses these issues.\nThe latency added by going through the shared database means that\ndeclarative APIs are best suited to latency-tolerant applications like\nhigh-level control.\n\n<!-- ## Concepts (describe resources, controllers, etc, and link to docs) -->\n\n## Prerequisites\n\n* Completed the [Quickstart Guide](../quickstart.md), after which the GCP project is set up and `gcloud-sdk` and `kubectl` are installed and configured.\n* `docker` is installed and configured on the workstation ([instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)).\n<!-- * For the last part of the guide: A robot that has been successfully [connected to the cloud](connecting-robot.md). -->\n\nCreate a directory for the code examples of this guide, e.g.:\n\n```shell\nmkdir charge-service\ncd charge-service\n```\n\nSet your GCP project ID as an environment variable:\n\n```\nexport PROJECT_ID=[YOUR_GCP_PROJECT_ID]\n```\n\nAll files created in this tutorial can be found in\n[docs/how-to/examples/charge-service](https://github.com/googlecloudrobotics/core/tree/master/docs/how-to/examples/charge-service).\nIf you download the files, you have to replace the placeholder `[PROJECT_ID]` with your GCP project ID:\n\n```shell\nsed -i \"s/\\[PROJECT_ID\\]/$PROJECT_ID/g\" charge-controller.yaml\n```\n\n## Installing metacontroller\n\nThis tutorial is based on [metacontroller](https://metacontroller.github.io/metacontroller/intro.html), an add-on for Kubernetes that makes it easy to write and deploy [custom controllers](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#custom-controllers).\nCustom controllers implement the logic behind a declarative API.\n\nFirst, make sure that `kubectl` points to the correct GKE cluster:\n\n```shell\nkubectl config get-contexts\n```\n\nIf the correct cluster is not marked with an asterisk in the output, you can switch to it with `kubectl config use-context [...]`.\n\nNow install metacontroller to the cloud-cluster:\n\n```shell\nkubectl create namespace metacontroller\nkubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-rbac.yaml\nkubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-crds-v1.yaml\nkubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller.yaml\n```\n\nLet's check that all resources came up:\n\n```console\n> kubectl get pods --namespace metacontroller\nNAME               READY   STATUS    RESTARTS   AGE\nmetacontroller-0   1/1     Running   0          1m\n```\n\nYou can learn more details in the metacontroller's [install instructions](https://metacontroller.github.io/metacontroller/guide/install.html).\n\n> **Limitations of metacontroller**:\n> Writing custom controllers with metacontroller is easy, and you can use\n> whatever programming language you prefer.\n> However, it has some limitations.\n>\n> 1. metacontroller can't directly detect changes in external state, although\n>    you can configure it to periodically reconcile your resources with external\n>    systems. This introduces latency corresponding to the reconciliation\n>    frequency.\n> 1. The information available to your controller is limited.\n>    You can't use metacontroller to create a controller that only acts on a\n>    single resource out of many (for example, a controller that only executes\n>    the highest-priority action).\n>\n> For advanced use cases, writing a controller in Golang offers more\n> flexibility. See\n> [sample-controller](https://github.com/kubernetes/sample-controller) for an\n> example.\n\n## Defining the controller logic\n\nThe core of a declarative API is the controller logic, which defines how the resources should be handled and reports the current status.\nFor the Charge Service, we've implemented the logic in a Python script.\nDownload [server.py](examples/charge-service/server.py):\n\n```shell\ncurl -O https://raw.githubusercontent.com/googlecloudrobotics/core/master/docs/how-to/examples/charge-service/server.py\n```\n\nThis Python program implements a server that listens on port 80 for incoming HTTP POST requests from metacontroller.\nThe controller logic is contained in the `sync()` method, which handles new ChargeActions by calling `charge_service.start_charging()`, and handles in-progress ChargeActions by updating the status.\n\n[embedmd]:# (examples/charge-service/server.py python /.*state = current_status.get/ /return.*status.*/)\n```python\n    state = current_status.get(\"state\", \"CREATED\")\n\n    if state == \"CREATED\":\n      # The ChargeAction has just been created. Use the external Charge Service\n      # to start charging. Store the request ID in the status so we can use it\n      # to check the state of the charge request.\n      request_id = self.charge_service.start_charging()\n      desired_status[\"state\"] = \"IN_PROGRESS\"\n      desired_status[\"request_id\"] = request_id\n\n    elif state == \"IN_PROGRESS\":\n      try:\n        # Get the progress of the charge request from the external service.\n        progress = self.charge_service.get_progress(\n            current_status[\"request_id\"])\n        desired_status[\"charge_level_percent\"] = progress\n\n        if progress == 100:\n          # Charging has completed.\n          desired_status[\"state\"] = \"OK\"\n\n      except ValueError as e:\n        # The charge request was not found. This could be because the robot was\n        # restarted during a charge, and the request was forgotten.\n        desired_status[\"state\"] = \"ERROR\"\n        desired_status[\"message\"] = str(e)\n\n    elif state in [\"OK\", \"CANCELLED\", \"ERROR\"]:\n      # Terminal state, do nothing.\n      pass\n\n    else:\n      desired_status[\"state\"] = \"ERROR\"\n      desired_status[\"message\"] = \"Unrecognized state: %r\" % state\n\n    return {\"status\": desired_status, \"children\": []}\n```\n\n## Dockerizing the service\n\nNext, to prepare our controller logic for deployment in the cloud, we package it as a Docker image. Make sure that the docker daemon is running and that your user has the necessary privileges:\n\n```shell\ndocker run --rm hello-world\n```\n\nIf this command fails, make sure Docker is installed according to the [installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/).\n\nIn the same directory as `server.py`, create a `Dockerfile` with the following contents:\n\n[embedmd]:# (examples/charge-service/Dockerfile dockerfile)\n```dockerfile\nFROM python:alpine\n\nWORKDIR /data\n\nCOPY server.py ./\n\nCMD [ \"python\", \"-u\", \"./server.py\" ]\n```\n\n(Note: the `-u` option disables line-buffering; Python's line-buffering can prevent output from appearing immediately in the Docker logs.)\n\nTo build the Docker image, run:\n\n```shell\ndocker build -t charge-controller .\n```\n\nYou should see `Successfully tagged charge-controller:latest`. You can run the container locally with:\n\n```shell\ndocker run -ti --rm -p 8000:8000 charge-controller\n```\n\nThen, from another terminal on the same workstation, send a request with an empty `parent` object:\n\n```shell\ncurl -X POST -d '{\"parent\": {}, \"children\": []}' http://localhost:8000/\n```\n\nYou should see a response like:\n\n```json\n{\"status\": {\"state\": \"IN_PROGRESS\", \"request_id\": \"2423e70c-9dc7-47ac-abcb-b2ef0cbc676c\"}, \"children\": []}\n```\n\nThe response indicates that the controller would set `\"state\": \"IN_PROGRESS\"` on a newly-created ChargeAction.\n\n## Uploading the Docker image to the cloud\n\nIn order to be able to run the server as a container in our cloud cluster, we need to upload the Docker image to our GCP project's private [container registry](https://cloud.google.com/container-registry/docs/pushing-and-pulling).\n\nEnable the Docker credential helper:\n\n```shell\ngcloud auth configure-docker\n```\n\nTag the image and push it to the registry:\n\n```shell\ndocker tag charge-controller gcr.io/$PROJECT_ID/charge-controller\ndocker push gcr.io/$PROJECT_ID/charge-controller\n```\n\nThe image should now show up in the [Container Registry](https://console.cloud.google.com/gcr).\n\n## Deploying the declarative API in the cloud\n\nCreate a file called `charge-crd.yaml` with the following contents:\n\n[embedmd]:# (examples/charge-service/charge-crd.yaml yaml)\n```yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chargeactions.example.com\n  annotations:\n    cr-syncer.cloudrobotics.com/spec-source: cloud\nspec:\n  group: example.com\n  names:\n    kind: ChargeAction\n    plural: chargeactions\n    singular: chargeaction\n  scope: Namespaced\n  versions:\n    - name: v1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          x-kubernetes-preserve-unknown-fields: true\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:cr-syncer:chartaction\n  labels:\n    cr-syncer.cloudrobotics.com/aggregate-to-robot-service: \"true\"\nrules:\n- apiGroups:\n  - example.com\n  resources:\n  - chargeactions\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n```\n\nThis is a [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) definition (CRD) for a resource called ChargeAction.\nThis simple example just describes the name and version of the API, but CRDs can also define schemas for the resources.\nThe ClusterRole configures [role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) to let the robot access the ChargeActions.\nDon't worry about the `cr-syncer.cloudrobotics.com/spec-source` annotation for now, as it'll be explained later in the tutorial.\n\nNext, create a file called `charge-controller.yaml` with the following contents, replacing `[PROJECT_ID]` with your GCP project ID:\n\n[embedmd]:# (examples/charge-service/charge-controller.yaml yaml)\n```yaml\napiVersion: metacontroller.k8s.io/v1alpha1\nkind: CompositeController\nmetadata:\n  name: charge-controller\nspec:\n  generateSelector: true\n  parentResource:\n    apiVersion: example.com/v1\n    resource: chargeactions\n  resyncPeriodSeconds: 1\n  hooks:\n    sync:\n      webhook:\n        url: http://charge-controller.default:8000/sync\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: charge-controller\nspec:\n  selector:\n    app: charge-controller\n  ports:\n  - port: 8000\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: charge-controller\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: charge-controller\n  template:\n    metadata:\n      labels:\n        app: charge-controller\n    spec:\n      containers:\n      - name: controller\n        image: gcr.io/[PROJECT_ID]/charge-controller\n        ports:\n        - containerPort: 8000\n```\n\nThis file contains the information needed by Kubernetes and metacontroller to handle ChargeAction resources.\nIn the following, we will go over it bit by bit assuming basic familiarity with the [YAML format](https://en.wikipedia.org/wiki/YAML).\n\nWe define three Kubernetes resources:\n\n* The *CompositeController* tells metacontroller to send ChargeAction resources to the charge-controller Service.\n* The *Service* defines how the HTTP server is exposed within the cluster.\n* The *Deployment* describes the Docker container to run.\n\nMetadata, labels, and selectors are used to tie the three resources together.\n\nA detailed explanation of the Kubernetes resources is out of scope for this guide, check out the [Kubernetes docs](https://kubernetes.io/docs/home/) or [metacontroller User Guide](https://metacontroller.github.io/metacontroller/guide.html) to get started.\nThere are a few points worth mentioning, though:\n\n* In the Deployment, don't forget to replace `[PROJECT_ID]` with your GCP project ID.\n* The CompositeController sets `resyncPeriodSeconds: 1`.\n  This tells metacontroller to check each ChargeAction every second.\n  This allows `server.py` to update the progress every second while the action is in progress.\n* The CompositeController sets `url: http://charge-controller.default:8000/sync`.\n  This tells metacontroller that the ChargeAction resources are handled by a service called `charge-controller` in the `default` namespace.\n\nDeploy these resources by applying the configuration:\n\n```shell\nkubectl apply -f charge-crd.yaml\nkubectl apply -f charge-controller.yaml\n```\n\nYou can explore the various resources that were created on your cluster as a result of this command in the [GKE Console](https://console.cloud.google.com/kubernetes/workload) or with `kubectl`, e.g.:\n\n```shell\nkubectl get pods\n```\n\nThe resulting list should contain a running pod with a name like `charge-controller-xxxxxxxxxx-xxxxx`.\n\n## Redeploying after a change\n\nIf you make a change to `server.py`, you need to rebuild and push the Docker image:\n\n```shell\ndocker build -t charge-controller .\ndocker tag charge-controller gcr.io/$PROJECT_ID/charge-controller\ndocker push gcr.io/$PROJECT_ID/charge-controller\n```\n\nThe easiest way to get Kubernetes to restart the workload with the latest version of the container is to delete the pod:\n\n```shell\nkubectl delete pod -l 'app=charge-controller'\n```\n\nKubernetes will automatically pull the newest image and recreate the pod.\n\nIf you make a change to `charge-controller.yaml`, all you have to do is apply it again:\n\n```shell\nkubectl apply -f charge-controller.yaml\n```\n\n## Accessing the API\n\nYou can use `kubectl` to interact with the API.\nCreate a file called `charge-action.yaml` with the following contents:\n\n[embedmd]:# (examples/charge-service/charge-action.yaml yaml)\n```yaml\napiVersion: example.com/v1\nkind: ChargeAction\nmetadata:\n  name: my-charge-action\n```\n\nRun the following command to create a ChargeAction and observe how its status changes:\n\n```shell\nkubectl apply -f charge-action.yaml \\\n  && watch -n0 kubectl describe chargeaction my-charge-action\n```\n\nOver the next 10 seconds, you should see the \"Charge Level Percent\" increase to 100, and then the state should become \"CHARGED\".\n\n> **Troubleshooting**:\n> If the ChargeAction's status doesn't change, check that metacontroller is installed by running `kubectl --namespace metacontroller get pods`.\n> You should see `metacontroller-0   1/1    Running`.\n> You can also check the metacontroller logs with `kubectl --namespace metacontroller logs metacontroller-0`\n\n\n## Deploying the declarative API on the robot.\n\nSo far, the Charge Service has been running in the cloud, but we need to run\ncode on the robot to get it to charge.\nWe can change this with the `cr-syncer`, a component of Cloud Robotics Core that allows declarative APIs to work between Kubernetes clusters.\nIn particular, we can run the charge-controller on the robot, while creating the ChargeAction in the cloud cluster.\nThe `cr-syncer` takes care of copying the ChargeAction to the robot when the\nrobot has network connectivity.\n\n**Prerequisite**: you'll need a robot that has been successfully [connected to the cloud](connecting-robot.md).\n\nFirst, remove the controller from the cloud cluster:\n\n```shell\n# Note: run this on the workstation\nkubectl delete -f charge-controller.yaml\n```\n\nThen SSH into the robot, install metacontroller, and bring up the charge-controller there:\n\n```shell\n# Note: run this on the robot\nkubectl create namespace metacontroller\nkubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-rbac.yaml\nkubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller-crds-v1.yaml\nkubectl apply -f https://raw.githubusercontent.com/metacontroller/metacontroller/master/manifests/production/metacontroller.yaml\n\nexport PROJECT_ID=[YOUR_GCP_PROJECT_ID]\nkubectl apply -f https://raw.githubusercontent.com/googlecloudrobotics/core/master/docs/how-to/examples/charge-service/charge-crd.yaml\ncurl https://raw.githubusercontent.com/googlecloudrobotics/core/master/docs/how-to/examples/charge-service/charge-controller.yaml \\\n  | sed \"s/\\[PROJECT_ID\\]/$PROJECT_ID/g\" | kubectl apply -f -\n```\n\nNow, check that these are running correctly:\n\n```console\n# Note: run this on the robot\n> kubectl get pods --namespace metacontroller\nNAME               READY   STATUS    RESTARTS   AGE\nmetacontroller-0   1/1     Running   0          1m\n> kubectl get pods -l app=charge-controller\nNAME                                 READY   STATUS    RESTARTS   AGE\ncharge-controller-57786849f8-xp5kf   1/1     Running   0          77s\n```\n\nSwitch back to a terminal on your workstation.\nAs before, you can create a ChargeAction with `kubectl`, but this time it will be\nhandled by the controller on the robot.\n\n```shell\n# Note: run this on the workstation\nkubectl delete -f charge-action.yaml\nkubectl apply -f charge-action.yaml \\\n  && watch -n0 kubectl describe chargeaction my-charge-action\n```\n\nHow does this work?\n\n- The `cr-syncer` runs on the robot and watches custom resources in the cloud.\n- It sees the `cr-syncer.cloudrobotics.com/spec-source: cloud` annotation on the\n  CustomResourceDefinition, which tells it to copy the `spec` from\n  `my-charge-action` in the cloud cluster into a copy of `my-charge-action` in\n  the robot cluster.\n- While the robot is charging, the robot's charge-controller updates the status\n  in the robot's cluster.\n- The `cr-syncer` copies the status back up to the original resource in the\n  cloud cluster.\n\n## Cleaning up\n\nIn order to stop the controller and remove the CRD you created, run:\n\n```shell\nkubectl delete -f charge-controller.yaml -f charge-crd.yaml\n```\n\nIf you want to uninstall metacontroller too, run:\n\n```shell\nkubectl delete namespace metacontroller\n```\n\nIf you installed on the robot, you'll need to run these commands there too.\n\n<!--\nTODO(rodrigoq): define \"What's Next\" for declarative APIs\n\n## What's next\n\nThere are a few inconvenient steps in this guide, e.g.:\n\n* manually replacing the project ID in all source files or\n* remotely logging in to the robot to start a workload.\n\nThis is where the app management layer comes in; it provides, among other capabilities:\n\n* ways of bundling and parameterizing Kubernetes resources and\n* remote management of Kubernetes resources/workloads on the robot.\n\nAlso note that both the server and the client side can be implemented with similar ease in other programming languages, such as [Go](https://github.com/googleapis/google-api-go-client).\n-->\n"
  },
  {
    "path": "docs/how-to/deploy-from-sources.md",
    "content": "# Deploy Cloud Robotics Core from sources\n\nEstimated time: 30 min\n\nThis page describes how to set up a Google Cloud Platform (GCP) project\ncontaining the Cloud Robotics Core components.\nIn particular, this creates a cluster with Google Kubernetes Engine and prepares\nit to accept connections from robots, which enables those robots to securely\ncommunicate with GCP.\nThe commands were tested on machines running Debian (Stretch) or Ubuntu (16.04\nand 18.04) Linux.\n\n1. In the GCP Console, go to the [Manage resources][resource-manager] page and\n   select or create a project.\n1. Make sure that [billing][modify-project] is enabled for your project.\n1. [Install the Cloud SDK][cloud-sdk]. When prompted, choose the project that you created above.\n1. After installing the Cloud SDK, install the `kubectl` command-line tool and the GKE auth plugin:\n\n    ```shell\n    gcloud components install kubectl\n    gcloud components gke-gcloud-auth-plugin\n    ```\n\n    If you're using Debian or Ubuntu, you may need to use `apt install kubectl` instead.\n\n1. [Install the Bazel build system][install-bazel].\n\n1. Install additional build dependencies:\n\n    ```shell\n    sudo apt-get install default-jdk git python-dev unzip xz-utils\n    ```\n\n[resource-manager]: https://console.cloud.google.com/cloud-resource-manager\n[modify-project]: https://cloud.google.com/billing/docs/how-to/modify-project\n[cloud-sdk]: https://cloud.google.com/sdk/docs/\n[install-bazel]: https://github.com/bazelbuild/bazel/blob/4.0.0/site/docs/install-ubuntu.md\n\n## Build and deploy the project\n\n1. Clone the source repo.\n\n    ```shell\n    git clone https://github.com/googlecloudrobotics/core\n    cd core\n    ```\n\n1. Create application default credentials, which are used to deploy the cloud project and\n   authorize access to the cloud docker registry.\n\n    ```shell\n    gcloud auth application-default login\n    gcloud auth configure-docker\n    ```\n\n1. Create a Cloud Robotics config in your project:\n\n    ```shell\n    ./deploy.sh set_config [PROJECT_ID]\n    ```\n\n    You can keep the defaults for the other settings by hitting `ENTER`.\n\n    This command creates a file `config.sh` containing your choices and stores\n    in into a cloud-storage bucket named `[PROJECT_ID]-cloud-robotics-config`.\n    You can verify the settings using:\n\n    ```shell\n    gcloud storage cat gs://[PROJECT_ID]-cloud-robotics-config/config.sh\n    ```\n\n\n1. Build the project. Depending on your computer and internet connection, it may take around 15 minutes.\n\n    ```shell\n    bazel build //...\n    ```\n\n1. Deploy the cloud project.\n\n    ```shell\n    ./deploy.sh create [PROJECT_ID]\n    ```\n\n> **Known issue:**\n> Sometimes, this command fails with an error message like\n> `Error 403: The caller does not have permission`,\n> `Error 403: Service ... not found or permission denied',\n> `Bad status during token exchange: 503`, or\n> `Error enabling service`.\n> In these cases, wait for a minute and try again.\n\n`deploy.sh` created a Kubernetes cluster using Google Kubernetes Engine and used Helm to install the Cloud Robotics Core components.\nYou can browse these components on the [Workloads dashboard](https://console.cloud.google.com/kubernetes/workload).\nAlternatively, you can list them from the console on your workstation:\n\n```console\n$ kubectl get pods\n\nNAME                READY   STATUS             RESTARTS   AGE\ncert-manager-xxx    1/1     Running            0          1m\nnginx-ingress-xxx   1/1     Running            0          1m\noauth2-proxy-xxx    0/1     CrashLoopBackOff   4          1m\ntoken-vendor-xxx    1/1     Running            0          1m\n```\n\n> **Note** Unless you already set up OAuth, the `oauth2-proxy` will show an error which we will ignore for now.\n\n\nIn addition to the cluster, `deploy.sh` also created:\n\n* the [[PROJECT_ID]-robot Cloud Storage bucket](https://console.cloud.google.com/storage/browser), containing the scripts that connect robots to the cloud, and\n* the [Identity & Access Management policies](https://console.cloud.google.com/iam-admin/iam) that authorize robots and humans to communicate with GCP.\n\nWith the project deployed, you're ready to [connect a robot to the cloud](connecting-robot.md).\n\n## Update the project\n\nTo apply changes made in the source code, run:\n\n```shell\n./deploy.sh update [PROJECT_ID]\n```\n\n## Clean up\n\nThe following command will delete:\n\n* the [cloud-robotics Kubernetes cluster](https://console.cloud.google.com/kubernetes/list)\n\nThis can be useful if the cluster is in a broken state.\nBe careful with this invocation, since you'll have to redeploy the project and reconnect any robots afterwards.\n\n```shell\n./deploy.sh delete [PROJECT_ID]\n```\n\nIf you want to completely shut down the project, see [the Resource Manager documentation](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects).\n\n## What's next\n\n* [Connecting a robot to the cloud](connecting-robot.md).\n* [Setting up OAuth for web UIs](setting-up-oauth.md).\n"
  },
  {
    "path": "docs/how-to/deploying-grpc-service.md",
    "content": "# Deploying a gRPC service written in C++\n\nEstimated time: 60 min\n\nIn this guide we will deploy a gRPC service written in C++ and deploy it to our Google Kubernetes Engine (GKE) cluster in the cloud in such a way that authentication is required for access. We will show how to access the service from the workstation and how to access it from code running in the robot's Kubernetes cluster.\n\n\n## Prerequisites\n\n* Completed the [Quickstart Guide](../quickstart.md), after which the GCP project is set up and `gcloud-sdk` and `kubectl` are installed and configured.\n* `docker` is installed and configured on the workstation ([instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)).\n* `git` is installed on the workstation.\n* For the last part of the guide: A robot that has been successfully [connected to the cloud](connecting-robot.md).\n\nAll files for this tutorial are located in\n[docs/how-to/examples/greeter-service/](https://github.com/googlecloudrobotics/core/tree/master/docs/how-to/examples/greeter-service).\n\n```shell\ngit clone https://github.com/googlecloudrobotics/core\ncd core/docs/how-to/examples/greeter-service\n```\n\nSet your GCP project ID as an environment variable:\n\n```shell\nexport PROJECT_ID=[YOUR_GCP_PROJECT_ID]\n```\n\n\n## Running gRPC server and client locally\n\nWe will use [gRPC's quickstart example](https://grpc.io/docs/quickstart/cpp.html) with small modifications.\nIf you like to learn more about gRPC in C++, follow their guide first.\n\nThe gRPC `helloworld.Greeter` service is defined in `proto/helloworld.proto`.\nIt accepts a `HelloRequest` containing a `name` and responds with a `HelloReply` containing a `message`.\nThe server is implemented in `server/server.cc` and the client is implemented `client/client.cc`. The client sends the request with `name: \"world\"` to the server which responds with `message: \"Hello <name>\"`.\n\nIn this tutorial, we build the server and client code inside Docker containers, so you don't need to install the gRPC library.\nIf you prefer, you can install the gRPC following [these instructions](https://github.com/grpc/grpc/blob/master/src/cpp/README.md) and build the server and client locally using the provided `Makefile`.\n\nMake sure the Docker daemon is running and your user has the necessary privileges:\n\n```shell\ndocker run --rm hello-world\n```\n\nIf this command fails, make sure Docker is installed according to the [installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/).\n\nThe Docker image for the server is configured in `server/Dockerfile`:\n\n[embedmd]:# (examples/greeter-service/server/Dockerfile dockerfile)\n```dockerfile\nFROM grpc/cxx:1.12.0\n\nWORKDIR /data\n\nCOPY server/server.cc ./server/\nCOPY proto/helloworld.proto ./proto/\nCOPY Makefile ./\n\nRUN make greeter-server && make clean\n\nCMD [\"./greeter-server\"]\n```\n\nWe use the [grpc/cxx](https://hub.docker.com/r/grpc/cxx) Docker image which contains all the build tools and libraries (`g++`, `make`, `protoc`, and `grpc`) we need to build the `greeter-server` binary.\nThe Docker image for the client is configured in `client/Dockerfile` which builds the `greeter-client` from `client/client.cc`.\n\nTo build the Docker images, run:\n\n```shell\ndocker build -t greeter-server -f server/Dockerfile .\ndocker build -t greeter-client -f client/Dockerfile .\n```\n\n> **Note**\n> The docker files are in the subfolders `greeter-server/server/` and `greeter-server/client`, but the docker command must be called from `greeter-server/` to include the files which are shared between the server and the client.\n\nYou should now have an image tagged `greeter-server` and one tagged `greeter-client` in your local registry:\n\n```shell\ndocker images | grep greeter\n```\n\nTo run the server locally, the container's port 50051, which specified as gRPC port in `server.cc`, has to be published to your machine with the flag `-p 50051:50051`:\n\n```shell\ndocker run --rm -p 50051:50051 --name greeter-server greeter-server\n```\n\nIn another console run the client container.\nThe flag `--network=host` tells the container to use your workstation's network stack which allows the client to connect to `localhost`.\n\n```shell\ndocker run --rm --network=host greeter-client ./greeter-client localhost\n```\n\nYou should see `Greeter received: Hello world` in the client's output and `Received request: name: \"world\"` in the server's output. You can also send your own name in the gRPC request to the server, try:\n\n```shell\ndocker run --rm --network=host greeter-client \\\n  ./greeter-client localhost $USER\n```\n\nYou can stop the server from another terminal by running:\n\n```shell\ndocker stop greeter-server\n```\n\n\n## Uploading the Docker image to the cloud\n\nIn order to be able to run the server as a container in our cloud cluster, we need to upload the Docker image to our GCP project's private [container registry](https://cloud.google.com/container-registry/docs/pushing-and-pulling).\n\nEnable the Docker credential helper:\n\n```shell\ngcloud auth configure-docker\n```\n\nTag the image and push it to the registry:\n\n```shell\ndocker tag greeter-server gcr.io/$PROJECT_ID/greeter-server\ndocker push gcr.io/$PROJECT_ID/greeter-server\n```\n\nThe image should now show up in the [container registry](https://console.cloud.google.com/gcr).\n\n\n## Deploying the service in the cloud using Kubernetes\n\nRun the following command to create `greeter-server.yaml` using the provided template:\n\n```shell\ncat greeter-server.yaml.tmpl | envsubst >greeter-server.yaml\n```\n\nThis file contains the information needed by Kubernetes to run the gRPC service in our cloud cluster.\nThe three resources, Ingress, Service, and Deployment, are explained in the [deploying a service tutorial](deploying-service.md).\nIn contrast to the other tutorial, the Ingress tells nginx to forward incoming requests to a gRPC backend.\n\n[embedmd]:# (examples/greeter-service/greeter-server.yaml.tmpl yaml /^/ /---/)\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: greeter-server-ingress\n  annotations:\n    nginx.ingress.kubernetes.io/backend-protocol: GRPC\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: \"www.endpoints.${PROJECT_ID}.cloud.goog\"\n    http:\n      paths:  # must match the namespace and service name in the proto\n      - path: /helloworld.Greeter/\n        pathType: Prefix\n        backend:\n          service:\n            name: greeter-server-service\n            # must match the port used in server.cc\n            port:\n              number: 50051\n---\n```\n\nMake sure that `kubectl` points to the correct GCP project:\n\n```shell\nkubectl config get-contexts\n```\n\nIf the correct cluster is not marked with an asterisk in the output, you can switch contexts with `kubectl config use-context [...]`.)\nThen deploy by applying the configuration:\n\n```shell\nkubectl apply -f greeter-server.yaml\n```\n\nYou can explore the various resources that were created on your cluster as a result of this command in the [GKE Console](https://console.cloud.google.com/kubernetes/workload) or with `kubectl`, e.g.:\n\n```shell\nkubectl get pods\n```\n\nThe resulting list should contain a running pod with a name like `greeter-server-xxxxxxxxxx-xxxxx`.\n\n\n## Redeploying after a change\n\nFor convenience, `deploy.sh` provides some commands to create, delete, and update the service.\nIf you make changes to `greeter-server.yaml.tmpl`, all you have to do is run:\n\n```shell\n./deploy.sh update_config\n```\n\nIf you make changes to `server.cc`, you need to run:\n\n```shell\n./deploy.sh update_server\n```\n\nThis builds, tags, and pushes the Docker image, and then forces a redeployment of the image by calling `kubectl delete pod -l 'app=greeter-server-app'`.\nIt also updates the resource definitions, so you don't have to run `./deploy.sh update_config` if you made changes to both files.\n\n\n## Accessing the API\n\nIn `client/client.cc` we use `grpc::InsecureChannelCredentials()` when talking to `localhost` while we use `grpc::GoogleDefaultCredentials()` when talking to any other address.\nSSL authentication with credentials from the user or robot are necessary when talking to the `greeter-server` in the Cloud Robotics project.\n\n[embedmd]:# (examples/greeter-service/client/client.cc c++ /^ +if.*localhost/ /^ +}$/)\n```c++\n  if (grpc_endpoint.find(\"localhost:\") == 0 ||\n      grpc_endpoint.find(\"127.0.0.1:\") == 0) {\n    channel_creds = grpc::InsecureChannelCredentials();\n  } else {\n    channel_creds = grpc::GoogleDefaultCredentials();\n  }\n```\n\nLet's try to access our server.\nWe have to connect to the nginx ingress which is hosted on `www.endpoints.$PROJECT_ID.cloud.goog:443`.\nTo ensure we have valid credentials to talk to nginx we have to mount our `~/.config` folder in the container.\n\n```shell\ndocker run --rm -v ~/.config:/root/.config greeter-client \\\n  ./greeter-client www.endpoints.$PROJECT_ID.cloud.goog:443 workstation\n```\n\nRecall that when running `./greeter-server` on your workstation you were able to see the server's log output upon receiving a request.\nThis log output is also recorded when the server is running in the cloud cluster. To inspect it, run:\n\n```shell\nkubectl logs -l 'app=greeter-server-app'\n```\n\nOr go to the [GKE Console](https://console.cloud.google.com/kubernetes/workload), select the `greeter-server` workload and click on \"Container logs\".\n\n\n## Accessing the API from the robot\n\nIn order to run `greeter-client` on the robot's Kubernetes cluster, we again package it as a Docker image and push it to our container registry, to which the robot also has access.\nOur deploy script offers a command to build, tag, and push the image to the cloud registry, like we did with the server container:\n\n```shell\n./deploy.sh push_client\n```\n\nAnd finally, to execute the script, SSH into robot and run:\n\n```shell\nexport PROJECT_ID=[YOUR_GCP_PROJECT_ID]\ndocker pull grpc/cxx:1.12.0  # This may take several minutes, depending on WiFi connection\nkubectl run -ti --rm --restart=Never --image=gcr.io/$PROJECT_ID/greeter-client greeter-client \\\n  -- ./greeter-client www.endpoints.$PROJECT_ID.cloud.goog:443 robot\n```\n\nYou should see the server's answer `Hello robot`.\n\nTwo things are noteworthy:\n\n* The `greeter-client` Docker image was pulled from the container registry without the need for additional credentials. This worked because there is a periodical job running on the robot's Kubernetes cluster that refreshes the GCR credentials. Run `kubectl get pods` on the robot and you will see pod names that start with `gcr-credential-refresher`.\n* `grpc::GoogleDefaultCredentials()` in the client's code automatically obtained credentials that allowed the robot to access the cloud cluster. This worked because the the local Metadata Server obtains access tokens for the robot in the background.\n\n\n## Cleaning up\n\nIn order to stop the service in the cloud cluster and revert the configuration changes, run:\n\n```shell\n./deploy.sh delete\n```\n"
  },
  {
    "path": "docs/how-to/deploying-service.md",
    "content": "# Deploying a service to the cloud cluster\n\nEstimated time: 60 min\n\nIn this guide we will write a HTTP service in Python and deploy it to our Google Kubernetes Engine (GKE) cluster in the cloud in such a way that authentication is required for access. We will show how to access the service from the workstation and how to access it from code running in the robot's Kubernetes cluster.\n\n## Prerequisites\n\n* Completed the [Quickstart Guide](../quickstart.md), after which the GCP project is set up and `gcloud-sdk` and `kubectl` are installed and configured.\n* `docker` is installed and configured on the workstation ([instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)).\n* `python3`, `python3-pip`, and `curl` are installed on the workstation.\n* For the last part of the guide: A robot that has been successfully [connected to the cloud](connecting-robot.md).\n\nCreate a directory for the code examples of this guide, e.g.:\n\n```shell\nmkdir hello-service\ncd hello-service\n```\n\nSet your GCP project ID as an environment variable:\n\n```shell\nexport PROJECT_ID=[YOUR_GCP_PROJECT_ID]\n```\n\nAll files created in this tutorial can be found in\n[docs/how-to/examples/hello-service/](https://github.com/googlecloudrobotics/core/tree/master/docs/how-to/examples/hello-service).\nIf you download the files, you have to replace the placeholders `[PROJECT_ID]` with your GCP project ID:\n\n```shell\nsed -i \"s/\\[PROJECT_ID\\]/$PROJECT_ID/g\" client/client.py server/hello-server.yaml\n```\n\n## A simple HTTP server\n\nCreate a subdirectory for the server code:\n\n```shell\nmkdir server\ncd server\n```\n\nPaste the following into a file called `server.py`:\n\n[embedmd]:# (examples/hello-service/server/server.py python)\n```python\nfrom http import server\nimport signal\nimport sys\n\n\nclass MyRequestHandler(server.BaseHTTPRequestHandler):\n  def do_GET(self):\n    print('Received a request')\n    self.send_response(200)\n    self.send_header('Content-Type', 'text/plain')\n    self.end_headers()\n    self.wfile.write(b'Server says hello!\\n')\n\n\ndef main():\n  # Terminate process when Kubernetes sends SIGTERM.\n  signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))\n\n  server_address = ('', 8000)\n  httpd = server.HTTPServer(server_address, MyRequestHandler)\n  httpd.serve_forever()\n\n\nif __name__ == '__main__':\n  main()\n```\n\nThis Python program implements a server that listens on port 8000 for incoming HTTP GET requests. When such a request is received, it prints a line to stdout and responds to the request with a short message.\n\nYou can try it out with:\n\n```shell\npython server.py\n```\n\nIf you see `ImportError: No module named http`, you are most likely using Python 2.x; try `python3` instead of `python`.)\n\nThen, from another terminal on the same workstation, run:\n\n```shell\ncurl -i http://localhost:8000\n```\n\nYou should see the headers indicating that the request was successful (`200 OK`) and the server's response message. You can also try entering `localhost:8000` in your browser's address bar.\n\n## Dockerizing the service\n\nNext, to prepare our Python program for deployment in the cloud, we package it as a Docker image.\n\nMake sure that the docker daemon is running and that your user has the necessary privileges:\n\n```shell\ndocker run --rm hello-world\n```\n\nIf this command fails, make sure Docker is installed according to the [installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/).\n\nIn the same directory as `server.py`, create a `Dockerfile` with the following contents:\n\n[embedmd]:# (examples/hello-service/server/Dockerfile dockerfile)\n```dockerfile\nFROM python:alpine\n\nWORKDIR /data\n\nCOPY server.py ./\n\nCMD [ \"python\", \"-u\", \"./server.py\" ]\n```\n\n(Note: the `-u` option disables line-buffering; Python's line-buffering can prevent output from appearing immediately in the Docker logs.)\n\nTo build the Docker image, run:\n\n```shell\ndocker build -t hello-server .\n```\n\nYou should now have an image tagged `hello-server` in your local registry:\n\n```shell\ndocker images | grep hello-server\n```\n\nIt can be run locally with:\n\n```shell\ndocker run -ti --rm -p 8000:8000 hello-server\n```\n\nYou should now be able to send requests to the server with `curl` as before.\n\n## Uploading the Docker image to the cloud\n\nIn order to be able to run the server as a container in our cloud cluster, we need to upload the Docker image to our GCP project's private [container registry](https://cloud.google.com/container-registry/docs/pushing-and-pulling).\n\nEnable the Docker credential helper:\n\n```shell\ngcloud auth configure-docker\n```\n\nTag the image and push it to the registry:\n\n```shell\ndocker tag hello-server gcr.io/$PROJECT_ID/hello-server\ndocker push gcr.io/$PROJECT_ID/hello-server\n```\n\nThe image should now show up in the [Container Registry](https://console.cloud.google.com/gcr).\n\n## Deploying the service in the cloud using Kubernetes\n\nCreate a file called `hello-server.yaml` with the following contents:\n\n[embedmd]:# (examples/hello-service/server/hello-server.yaml yaml)\n```yaml\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: hello-server-ingress\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: www.endpoints.[PROJECT_ID].cloud.goog\n    http:\n      paths:\n      - path: /apis/hello-server\n        pathType: Prefix\n        backend:\n          service:\n            name: hello-server-service\n            port:\n              number: 8000\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hello-server-service\nspec:\n  ports:\n  - name: hello-server-port\n    port: 8000\n  # the selector is used to link pods to services\n  selector:\n    app: hello-server-app\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hello-server\nspec:\n  # all pods matching this selector belong to this deployment\n  selector:\n    matchLabels:\n      app: hello-server-app\n  template:\n    metadata:\n      # the other side of the link between services and pods\n      labels:\n        app: hello-server-app\n    spec:\n      containers:\n      - name: hello-server\n        image: gcr.io/[PROJECT_ID]/hello-server:latest\n        ports:\n        # must match the port of the service\n        - containerPort: 8000\n```\n\nThis file contains the information needed by Kubernetes to run our HTTP service in our cloud cluster. In the following, we will go over it bit by bit assuming basic familiarity with the [YAML format](https://en.wikipedia.org/wiki/YAML).\n\nWe define three Kubernetes resources:\n\n* The *Ingress* contains rules that tell our cluster's nginx (the HTTP server that handles all incoming traffic) which incoming requests to forward to our service.\n* The *Service* defines how our service is exposed within the cluster.\n* The *Deployment* describes the Docker container to run.\n\nMetadata, labels, and selectors are used to tie the three resources together.\n\nA detailed explanation of the Kubernetes resources is out of scope for this guide, check out the [Kubernetes docs](https://kubernetes.io/docs/home/) to get started. There are a few points worth mentioning, though:\n\n* In the Ingress and Deployment, don't forget to replace `[PROJECT_ID]` with your GCP project ID.\n* In the Ingress, there is an annotation with key `nginx.ingress.kubernetes.io/auth-url`. This tells our cluster's nginx to check the authorization of each request before forwarding it to the `hello-server`. The value `http://token-vendor...` is the cluster-internal DNS address of a token verifier service that is running in the cluster as part of the Cloud Robotics Core platform.\n* The Ingress rules specify that our hello-server will be reachable at `https://www.endpoints.[PROJECT_ID].cloud.goog/apis/hello-server`.\n* The Deployment contains the full reference of the Docker image that we pushed to the Container Registry in the previous section.\n\nMake sure that `kubectl` points to the correct GCP project:\n\n```shell\nkubectl config get-contexts\n```\n\nIf the correct cluster is not marked with an asterisk in the output, you can switch to it with `kubectl config use-context [...]`.)\nThen deploy by applying the configuration:\n\n```shell\nkubectl apply -f hello-server.yaml\n```\n\nYou can explore the various resources that were created on your cluster as a result of this command in the [GKE Console](https://console.cloud.google.com/kubernetes/workload) or with `kubectl`, e.g.:\n\n```shell\nkubectl get pods\n```\n\nThe resulting list should contain a running pod with a name like `hello-server-xxxxxxxxxx-xxxxx`.\n\n## Redeploying after a change\n\nIf you make a change to `server.py`, you need to rebuild and push the Docker image:\n\n```shell\ndocker build -t hello-server .\ndocker tag hello-server gcr.io/$PROJECT_ID/hello-server\ndocker push gcr.io/$PROJECT_ID/hello-server\n```\n\nThe easiest way to get Kubernetes to restart the workload with the latest version of the container is to delete the pod:\n\n```shell\nkubectl delete pod -l 'app=hello-server-app'\n```\n\nKubernetes will automatically pull the newest image and recreate the pod.\n\nIf you make a change to `hello-server.yaml`, all you have to do is apply it again:\n\n```shell\nkubectl apply -f hello-server.yaml\n```\n\n## Accessing the API\n\nLet's try to access our server as we did before:\n\n```shell\ncurl -i https://www.endpoints.$PROJECT_ID.cloud.goog/apis/hello-server\n```\n\nThis should result in a `401 Unauthorized` error because we did not supply any authorization information with the request.\n(Note: If you comment out the `auth-url` annotation in the Ingress definition and reapply it, this request will succeed.)\n\nWe can, however, easily obtain credentials from `gcloud` and attach them to our request by means of an \"Authorization\" header:\n\n```shell\ntoken=$(gcloud auth application-default print-access-token)\ncurl -i -H \"Authorization: Bearer $token\" https://www.endpoints.$PROJECT_ID.cloud.goog/apis/hello-server\n```\n\nIf this command fails because \"Application Default Credentials are not available\", you need to first run:\n\n```shell\ngcloud auth application-default login --project=$PROJECT_ID\n```\n\nAnd follow the instructions in your browser.\n\nRecall that when running `server.py` on your workstation you were able to see the server's log output upon receiving a request. This log output is also recorded when the server is running in the cloud cluster. To inspect it, run:\n\n```shell\nkubectl logs -l 'app=hello-server-app'\n```\n\nOr go to the [GKE Console](https://console.cloud.google.com/kubernetes/workload), select the `hello-server` workload and click on \"Container logs\".\n\nNext, let's access the API from some Python code. Eventually, we will build another Docker image from this code, so it needs to live in a separate directory:\n\n```shell\ncd ..\nmkdir client\ncd client\n```\n\nGet some dependencies:\n\n```shell\npip3 install --upgrade google-auth requests\n```\n\n(Depending on your local installation, you might have to use `pip3`.)\n\nCreate `client.py` with the following contents:\n\n[embedmd]:# (examples/hello-service/client/client.py python)\n```python\nimport google.auth\nimport google.auth.transport.requests as requests\n\ncredentials, project_id = google.auth.default()\n\nauthed_session = requests.AuthorizedSession(credentials)\n\nresponse = authed_session.request(\n  \"GET\", \"https://www.endpoints.[PROJECT_ID].cloud.goog/apis/hello-server\")\n\nprint(response.status_code, response.reason, response.text)\n```\n\nReplace `[PROJECT_ID]` with your GCP project ID.\n\nThis script:\n\n* uses [`google-auth`](https://google-auth.readthedocs.io/en/latest/user-guide.html) to obtain application default credentials (just as we previously did with the `gcloud` CLI),\n* uses the [`requests`](http://docs.python-requests.org/en/stable/) library to perform an authenticated request to our API,\n* prints the response to stdout.\n\nTry it out:\n\n```shell\npython3 client.py\n```\n\nYou will get a warning about using end user credentials. You can safely ignore this warning; we will eventually be using a robot's credentials.)\n\n## Accessing the API from the robot\n\nIn order to run this script on the robot's Kubernetes cluster, we again package it as a Docker image and push it to our container registry, to which the robot also has access.\n\nCreate a `Dockerfile` containing:\n\n[embedmd]:# (examples/hello-service/client/Dockerfile dockerfile)\n```dockerfile\nFROM python:alpine\n\nRUN pip install --no-cache-dir google-auth requests\n\nWORKDIR /data\n\nCOPY client.py ./\n\nCMD [ \"python\", \"-u\", \"./client.py\" ]\n```\n\nBuild, tag, and push the image:\n\n```shell\ndocker build -t hello-client .\ndocker tag hello-client gcr.io/$PROJECT_ID/hello-client\ndocker push gcr.io/$PROJECT_ID/hello-client\n```\n\nAnd finally, to execute the script, SSH into robot and run:\n\n```shell\nkubectl run -ti --rm --restart=Never --image=gcr.io/$PROJECT_ID/hello-client hello-client\n```\n\nYou should see the server's message.\n\nTwo things are noteworthy:\n\n* The `hello-client` Docker image was pulled from the Container Registry without the need for additional credentials. This worked because there is a periodical job running on the robot's Kubernetes cluster that refreshes the GCR credentials. Run `kubectl get pods` on the robot and you will see pod names that start with `gcr-credential-refresher`.\n* The `google.auth.default()` invocation in the Python code automatically obtained credentials that allowed the robot to access the cloud cluster. This worked because the `google-auth` library queried the local Metadata Server, which obtains access tokens for the robot in the background.\n\n## Cleaning up\n\nIn order to stop the service in the cloud cluster and revert the configuration changes, change to the `server` directory and run:\n\n```shell\nkubectl delete -f hello-server.yaml\n```\n\n<!--\n## What's next\n\nThere are a few inconvenient steps in this guide, e.g.:\n\n* manually replacing the project ID in all source files or\n* remotely logging in to the robot to start a workload.\n\nThis is where the app management layer comes in; it provides, among other capabilities:\n\n* ways of bundling and parameterizing Kubernetes resources and\n* remote management of Kubernetes resources/workloads on the robot.\n\nAlso note that both the server and the client side can be implemented with similar ease in other programming languages, such as [Go](https://github.com/googleapis/google-api-go-client).\n-->\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/Dockerfile",
    "content": "FROM python:alpine\n\nWORKDIR /data\n\nCOPY server.py ./\n\nCMD [ \"python\", \"-u\", \"./server.py\" ]\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/charge-action.yaml",
    "content": "apiVersion: example.com/v1\nkind: ChargeAction\nmetadata:\n  name: my-charge-action\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/charge-controller.yaml",
    "content": "apiVersion: metacontroller.k8s.io/v1alpha1\nkind: CompositeController\nmetadata:\n  name: charge-controller\nspec:\n  generateSelector: true\n  parentResource:\n    apiVersion: example.com/v1\n    resource: chargeactions\n  resyncPeriodSeconds: 1\n  hooks:\n    sync:\n      webhook:\n        url: http://charge-controller.default:8000/sync\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: charge-controller\nspec:\n  selector:\n    app: charge-controller\n  ports:\n  - port: 8000\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: charge-controller\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: charge-controller\n  template:\n    metadata:\n      labels:\n        app: charge-controller\n    spec:\n      containers:\n      - name: controller\n        image: gcr.io/[PROJECT_ID]/charge-controller\n        ports:\n        - containerPort: 8000\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/charge-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chargeactions.example.com\n  annotations:\n    cr-syncer.cloudrobotics.com/spec-source: cloud\nspec:\n  group: example.com\n  names:\n    kind: ChargeAction\n    plural: chargeactions\n    singular: chargeaction\n  scope: Namespaced\n  versions:\n    - name: v1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          x-kubernetes-preserve-unknown-fields: true\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:cr-syncer:chartaction\n  labels:\n    cr-syncer.cloudrobotics.com/aggregate-to-robot-service: \"true\"\nrules:\n- apiGroups:\n  - example.com\n  resources:\n  - chargeactions\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n"
  },
  {
    "path": "docs/how-to/examples/charge-service/server.py",
    "content": "from http.server import BaseHTTPRequestHandler, HTTPServer\nimport json\nimport signal\nimport sys\nimport time\nimport uuid\n\n\nclass Controller(BaseHTTPRequestHandler):\n  def sync(self, parent, children):\n    \"\"\"Actuate a ChargeAction custom resource.\n\n    The resource is actuated by using the Charge Service to send the robot to a\n    charger. While charging is in progress, the status of the resource is\n    updated to reflect the charge level.\n\n    Because the ChargeAction is handled by an external service, this controller\n    doesn't create any child resources, ie the `children` list is empty.\n\n    Args:\n      parent: The current ChargeAction resource.\n      children: Unused.\n\n    Returns:\n      A dict containing the latest status of the action, and an empty list of\n      children, eg:\n      {\n        \"status\": {\n          \"state\": \"OK\",\n        },\n        \"children\": [],\n      }\n    \"\"\"\n\n    # Get current status and copy to start building next status.\n    current_status = parent.get(\"status\", None) or {}\n    desired_status = dict(current_status)\n    state = current_status.get(\"state\", \"CREATED\")\n\n    if state == \"CREATED\":\n      # The ChargeAction has just been created. Use the external Charge Service\n      # to start charging. Store the request ID in the status so we can use it\n      # to check the state of the charge request.\n      request_id = self.charge_service.start_charging()\n      desired_status[\"state\"] = \"IN_PROGRESS\"\n      desired_status[\"request_id\"] = request_id\n\n    elif state == \"IN_PROGRESS\":\n      try:\n        # Get the progress of the charge request from the external service.\n        progress = self.charge_service.get_progress(\n            current_status[\"request_id\"])\n        desired_status[\"charge_level_percent\"] = progress\n\n        if progress == 100:\n          # Charging has completed.\n          desired_status[\"state\"] = \"OK\"\n\n      except ValueError as e:\n        # The charge request was not found. This could be because the robot was\n        # restarted during a charge, and the request was forgotten.\n        desired_status[\"state\"] = \"ERROR\"\n        desired_status[\"message\"] = str(e)\n\n    elif state in [\"OK\", \"CANCELLED\", \"ERROR\"]:\n      # Terminal state, do nothing.\n      pass\n\n    else:\n      desired_status[\"state\"] = \"ERROR\"\n      desired_status[\"message\"] = \"Unrecognized state: %r\" % state\n\n    return {\"status\": desired_status, \"children\": []}\n\n  def do_POST(self):\n    \"\"\"Serve the sync() function as a JSON webhook.\"\"\"\n    observed = json.loads(self.rfile.read(int(self.headers[\"content-length\"])))\n    desired = self.sync(observed[\"parent\"], observed[\"children\"])\n\n    self.send_response(200)\n    self.send_header(\"Content-type\", \"application/json\")\n    self.end_headers()\n    self.wfile.write(json.dumps(desired).encode('utf-8'))\n\n\nclass ChargeService(object):\n  \"\"\"ChargeService wraps an external API that send the robot to a charger.\n\n  For this example, it just fakes the charging process.\n  \"\"\"\n\n  SECONDS_FOR_FULL_CHARGE = 10\n\n  def __init__(self):\n    self._requests = {}\n\n  def start_charging(self):\n    request_id = str(uuid.uuid4())\n    self._requests[request_id] = time.time()\n    return request_id\n\n  def get_progress(self, request_id):\n    if request_id not in self._requests:\n      raise ValueError(\"invalid request ID\")\n\n    charge_time = time.time() - self._requests[request_id]\n    if charge_time > self.SECONDS_FOR_FULL_CHARGE:\n      return 100\n    else:\n      return int(100 * (charge_time / self.SECONDS_FOR_FULL_CHARGE))\n\n\n# Terminate process when Kubernetes sends SIGTERM.\nsignal.signal(signal.SIGTERM, lambda *_: sys.exit(0))\n\nController.charge_service = ChargeService()\nHTTPServer((\"\", 8000), Controller).serve_forever()\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/Makefile",
    "content": "#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nHOST_SYSTEM = $(shell uname | cut -f 1 -d_)\nSYSTEM ?= $(HOST_SYSTEM)\nCXX = g++\nCPPFLAGS += `pkg-config --cflags protobuf grpc`\nCXXFLAGS += -std=c++11 -I .\nifeq ($(SYSTEM),Darwin)\nLDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\\\n           -lgrpc++_reflection\\\n           -ldl\nelse\nLDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\\\n           -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\\\n           -ldl\nendif\nPROTOC = protoc\nGRPC_CPP_PLUGIN = grpc_cpp_plugin\nGRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`\n\nPROTOS_PATH = ./proto/\n\nvpath %.proto $(PROTOS_PATH)\n\nall: greeter-server greeter-client\n\ngreeter-server: helloworld.pb.o helloworld.grpc.pb.o server/server.o\n\t$(CXX) $^ $(LDFLAGS) -o $@\n\ngreeter-client: helloworld.pb.o helloworld.grpc.pb.o client/client.o\n\t$(CXX) $^ $(LDFLAGS) -o $@\n\n.PRECIOUS: %.grpc.pb.cc\n%.grpc.pb.cc: %.proto\n\t$(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<\n\n.PRECIOUS: %.pb.cc\n%.pb.cc: %.proto\n\t$(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<\n\nclean:\n\trm -f *.o client/*.o server/*.o *.pb *.pb.cc *.pb.h\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/client/Dockerfile",
    "content": "FROM grpc/cxx:1.12.0\n\nWORKDIR /data\n\nCOPY client/client.cc ./client/\nCOPY proto/helloworld.proto ./proto/\nCOPY Makefile ./\n\nRUN make greeter-client && make clean\n\nCMD [\"./greeter-client\"]\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/client/client.cc",
    "content": "/*\n *\n * Copyright 2019 The Cloud Robotics Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n#include <iostream>\n#include <memory>\n#include <string>\n\n#include <grpcpp/grpcpp.h>\n\n#include \"helloworld.grpc.pb.h\"\n\nusing grpc::Channel;\nusing grpc::ChannelCredentials;\nusing grpc::ClientContext;\nusing grpc::Status;\nusing helloworld::Greeter;\nusing helloworld::HelloReply;\nusing helloworld::HelloRequest;\n\nclass GreeterClient {\n public:\n  GreeterClient(std::shared_ptr<Channel> channel)\n      : stub_(Greeter::NewStub(channel)) {}\n\n  // Assembles the client's payload, sends it and presents the response back\n  // from the server.\n  std::string SayHello(const std::string& user) {\n    // Data we are sending to the server.\n    HelloRequest request;\n    request.set_name(user);\n\n    // Container for the data we expect from the server.\n    HelloReply reply;\n\n    // Context for the client. It could be used to convey extra information to\n    // the server and/or tweak certain RPC behaviors.\n    ClientContext context;\n\n    // The actual RPC.\n    Status status = stub_->SayHello(&context, request, &reply);\n\n    // Act upon its status.\n    if (status.ok()) {\n      return reply.message();\n    } else {\n      std::cout << status.error_code() << \": \" << status.error_message()\n                << std::endl;\n      return \"RPC failed\";\n    }\n  }\n\n private:\n  std::unique_ptr<Greeter::Stub> stub_;\n};\n\nint main(int argc, char** argv) {\n  if (argc < 2) {\n    const std::string client_path(argv[0]);\n    std::cout << \"Usage:\" << std::endl;\n    std::cout << \"  \" << client_path << \" <address[:port]> [<name>]\"\n              << std::endl;\n    std::cout << \"Example:\" << std::endl;\n    std::cout << \"  \" << client_path\n              << \" www.endpoints.${PROJECT_ID}.cloud.goog:443\" << std::endl;\n    return 0;\n  }\n\n  // The first parameter is the server's address, optionally containing the\n  // port.\n  std::string grpc_endpoint(argv[1]);\n  if (grpc_endpoint.find(\":\") == std::string::npos) {\n    // Set the default port of the server.\n    grpc_endpoint += \":50051\";\n  }\n\n  // The optional second parameter is the name to be sent to the server.\n  std::string name(\"world\");\n  if (argc >= 3) {\n    name = argv[2];\n  }\n\n  std::cout << \"Sending request to \" << grpc_endpoint << \" ...\" << std::endl;\n\n  // We are communicating via SSL to the endpoint service using the credentials\n  // of the user or robot running the client.\n  // We don't use credentials when connecting to localhost for testing.\n  std::shared_ptr<ChannelCredentials> channel_creds;\n  if (grpc_endpoint.find(\"localhost:\") == 0 ||\n      grpc_endpoint.find(\"127.0.0.1:\") == 0) {\n    channel_creds = grpc::InsecureChannelCredentials();\n  } else {\n    channel_creds = grpc::GoogleDefaultCredentials();\n  }\n\n  GreeterClient greeter(grpc::CreateChannel(grpc_endpoint, channel_creds));\n  std::string user(name);\n  std::string reply = greeter.SayHello(user);\n  std::cout << \"Greeter received: \" << reply << std::endl;\n\n  return 0;\n}\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/deploy.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -o pipefail -o errexit\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\ncd ${DIR}\n\nfunction die {\n  echo \"$1\" >&2\n  exit 1\n}\n\nfunction push_image {\n  local target=$1\n\n  docker build -f \"${target}/Dockerfile\" -t \"greeter-${target}\" .\n  docker tag \"greeter-${target}\" \"gcr.io/${PROJECT_ID}/greeter-${target}\"\n  docker push \"gcr.io/${PROJECT_ID}/greeter-${target}\"\n}\n\nfunction create_config {\n  cat greeter-server.yaml.tmpl | envsubst >greeter-server.yaml\n}\n\n# public functions\nfunction push_client {\n  push_image client\n}\n\nfunction update_config {\n  create_config\n  kubectl apply -f greeter-server.yaml\n}\n\nfunction update_server {\n  push_image server\n  kubectl delete pod -l 'app=greeter-server-app'\n  update_config\n}\n\nfunction create {\n  push_image server\n  push_client\n  update_config\n}\n\nfunction delete {\n  create_config\n  kubectl delete -f greeter-server.yaml\n}\n\n# main\nif [[ -z ${PROJECT_ID} ]]; then\n  die \"Set PROJECT_ID first: export PROJECT_ID=[GCP project id]\"\nfi\n\nif [[ ! \"$1\" =~ ^(create|delete|update_config|update_server|push_client)$ ]]; then\n  die \"Usage: $0 {create|delete|update_config|update_server|push_client}\"\nfi\n\n# call arguments verbatim:\n\"$@\"\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/greeter-server.yaml.tmpl",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: greeter-server-ingress\n  annotations:\n    nginx.ingress.kubernetes.io/backend-protocol: GRPC\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: \"www.endpoints.${PROJECT_ID}.cloud.goog\"\n    http:\n      paths:  # must match the namespace and service name in the proto\n      - path: /helloworld.Greeter/\n        pathType: Prefix\n        backend:\n          service:\n            name: greeter-server-service\n            # must match the port used in server.cc\n            port:\n              number: 50051\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: greeter-server-service\nspec:\n  ports:\n    - # optional descriptive name for the service port\n      name: grpc-port\n      # must match the service port specified in ingress\n      port: 50051\n  # the selector is used to link pods to services\n  selector:\n    app: greeter-server-app\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: greeter-server\nspec:\n  replicas: 1\n  # all pods matching this selector belong to this deployment\n  selector:\n    matchLabels:\n      app: greeter-server-app\n  template:\n    metadata:\n      # the other side of the link between services and pods\n      labels:\n        app: greeter-server-app\n    spec:\n      containers:\n      - name: greeter-server\n        image: \"gcr.io/${PROJECT_ID}/greeter-server:latest\"\n        ports:\n          # must match the port of the service\n          - containerPort: 50051\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/proto/helloworld.proto",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage helloworld;\n\n// The greeting service definition.\nservice Greeter {\n  // Sends a greeting.\n  rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\n\n// The request message containing the user's name.\nmessage HelloRequest {\n  string name = 1;\n}\n\n// The response message containing the greetings.\nmessage HelloReply {\n  string message = 1;\n}\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/server/Dockerfile",
    "content": "FROM grpc/cxx:1.12.0\n\nWORKDIR /data\n\nCOPY server/server.cc ./server/\nCOPY proto/helloworld.proto ./proto/\nCOPY Makefile ./\n\nRUN make greeter-server && make clean\n\nCMD [\"./greeter-server\"]\n"
  },
  {
    "path": "docs/how-to/examples/greeter-service/server/server.cc",
    "content": "/*\n *\n * Copyright 2019 The Cloud Robotics Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n#include <csignal>\n#include <future>\n#include <iostream>\n#include <memory>\n#include <string>\n#include <thread>\n\n#include <grpcpp/grpcpp.h>\n\n#include \"helloworld.grpc.pb.h\"\n\nusing grpc::Server;\nusing grpc::ServerBuilder;\nusing grpc::ServerContext;\nusing grpc::Status;\nusing helloworld::Greeter;\nusing helloworld::HelloReply;\nusing helloworld::HelloRequest;\n\n// The gRPC server is defined globally so that SIGTERM handler can shut it\n// down when Kubernetes stops the process.\nstd::unique_ptr<Server> server;\n\n// Logic and data behind the server's behavior.\nclass GreeterServiceImpl final : public Greeter::Service {\n  Status SayHello(ServerContext* context, const HelloRequest* request,\n                  HelloReply* reply) override {\n    std::cout << \"Received request: \" << request->ShortDebugString()\n              << std::endl;\n    std::string prefix(\"Hello \");\n    reply->set_message(prefix + request->name());\n    return Status::OK;\n  }\n};\n\nvoid RunServer() {\n  std::string server_address(\"0.0.0.0:50051\");\n  GreeterServiceImpl service;\n\n  ServerBuilder builder;\n  // Listen on the given address without any authentication mechanism. Cloud\n  // Robotics Core ensures that clients are authenticated.\n  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());\n  // Register \"service\" as the instance through which we'll communicate with\n  // clients. In this case it corresponds to a *synchronous* service.\n  builder.RegisterService(&service);\n  // Finally assemble the server.\n  server = builder.BuildAndStart();\n  std::cout << \"Server listening on \" << server_address << std::endl;\n\n  std::signal(SIGTERM, [](int) {\n    // When SIGTERM is received, shutdown the gRPC server.\n    server->Shutdown();\n  });\n\n  // Wait for the server to shutdown.\n  server->Wait();\n}\n\nint main(int argc, char** argv) {\n  RunServer();\n\n  return 0;\n}\n"
  },
  {
    "path": "docs/how-to/examples/hello-service/client/Dockerfile",
    "content": "FROM python:alpine\n\nRUN pip install --no-cache-dir google-auth requests\n\nWORKDIR /data\n\nCOPY client.py ./\n\nCMD [ \"python\", \"-u\", \"./client.py\" ]\n"
  },
  {
    "path": "docs/how-to/examples/hello-service/client/client.py",
    "content": "import google.auth\nimport google.auth.transport.requests as requests\n\ncredentials, project_id = google.auth.default()\n\nauthed_session = requests.AuthorizedSession(credentials)\n\nresponse = authed_session.request(\n  \"GET\", \"https://www.endpoints.[PROJECT_ID].cloud.goog/apis/hello-server\")\n\nprint(response.status_code, response.reason, response.text)\n"
  },
  {
    "path": "docs/how-to/examples/hello-service/server/Dockerfile",
    "content": "FROM python:alpine\n\nWORKDIR /data\n\nCOPY server.py ./\n\nCMD [ \"python\", \"-u\", \"./server.py\" ]\n"
  },
  {
    "path": "docs/how-to/examples/hello-service/server/hello-server.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: hello-server-ingress\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: www.endpoints.[PROJECT_ID].cloud.goog\n    http:\n      paths:\n      - path: /apis/hello-server\n        pathType: Prefix\n        backend:\n          service:\n            name: hello-server-service\n            port:\n              number: 8000\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hello-server-service\nspec:\n  ports:\n  - name: hello-server-port\n    port: 8000\n  # the selector is used to link pods to services\n  selector:\n    app: hello-server-app\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hello-server\nspec:\n  # all pods matching this selector belong to this deployment\n  selector:\n    matchLabels:\n      app: hello-server-app\n  template:\n    metadata:\n      # the other side of the link between services and pods\n      labels:\n        app: hello-server-app\n    spec:\n      containers:\n      - name: hello-server\n        image: gcr.io/[PROJECT_ID]/hello-server:latest\n        ports:\n        # must match the port of the service\n        - containerPort: 8000\n"
  },
  {
    "path": "docs/how-to/examples/hello-service/server/server.py",
    "content": "from http import server\nimport signal\nimport sys\n\n\nclass MyRequestHandler(server.BaseHTTPRequestHandler):\n  def do_GET(self):\n    print('Received a request')\n    self.send_response(200)\n    self.send_header('Content-Type', 'text/plain')\n    self.end_headers()\n    self.wfile.write(b'Server says hello!\\n')\n\n\ndef main():\n  # Terminate process when Kubernetes sends SIGTERM.\n  signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))\n\n  server_address = ('', 8000)\n  httpd = server.HTTPServer(server_address, MyRequestHandler)\n  httpd.serve_forever()\n\n\nif __name__ == '__main__':\n  main()\n"
  },
  {
    "path": "docs/how-to/running-ros-node.md",
    "content": "# Running a ROS node as a Kubernetes deployment\n\nEstimated time: 10 min\n\nThe following instructions describe how to setup a Kubernetes cluster on a robot\nrunning Ubuntu 20.04 and run a ROS node on it.\n\nThe installation script installs and configures:\n\n* Docker\n* A single-node Kubernetes cluster (packages: kubectl, kubeadm, kubelet)\n\nOnce you've done this, you can use Kubernetes to:\n\n* Reduce downtime during updates with Kubernetes deployments\n* Apply CPU, disk or memory quotas to individual processes\n* Add additional compute nodes to the cluster, such as an Nvidia Jetson\n* Use a network plugin to apply network access control\n* Manage project configuration or sensitive secrets such as account credentials\n\nFor more details, refer to the [Kubernetes documentation](https://kubernetes.io/docs/home/).\n\n## Installing the cluster on the robot\n\nSee <https://github.com/googlecloudrobotics/core/blob/main/docs/how-to/connecting-robot.md#installing-the-cluster-on-the-robot>.\n\n## Run a ROS node with Kubernetes\n\nIf you're already using ROS on your robot, you can run a ROS node inside Kubernetes that will communicate with other nodes on the robot. If not, you can follow the [ROS tutorials](http://wiki.ros.org/ROS/Tutorials) to get started.\n\nFirst, make sure you're running `roscore`. In another terminal, please run:\n\n```shell\nroscore\n```\n\n> **Caution:** If you have a more complicated ROS setup, such as a ROS master running on another machine, you might need to change `ROS_MASTER_URI` or `ROS_IP` in rostopic-echo.yaml.\n\nYou can run a ROS node by creating a Kubernetes Deployment object, and you can describe a Deployment in a YAML file.\nFor example, this YAML file describes a Deployment that runs `rostopic echo`.\nCreate file called `rostopic-echo.yaml` with the following contents:\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: rostopic-echo\nspec:\n  selector:\n    matchLabels:\n      app: rostopic-echo\n  template:\n    metadata:\n      labels:\n        app: rostopic-echo\n    spec:\n      containers:\n      - name: rostopic-echo\n        image: ros:melodic-ros-core\n        args:\n        - rostopic\n        - echo\n        - chatter\n        env:\n        - name: ROS_MASTER_URI\n          value: http://192.168.9.1:11311\n        - name: ROS_IP\n          value: 192.168.9.1\n      hostNetwork: true\n```\n\n> **Note:** For simplicity, this example uses `hostNetwork: true` to disable network isolation. Advanced users can disable host networking to improve security. For more information, see the networking documentation for <a href=\"http://wiki.ros.org/ROS/NetworkSetup\">Docker</a>, <a href=\"https://kubernetes.io/docs/concepts/cluster-administration/networking/\">Kubernetes</a> and <a href=\"http://wiki.ros.org/ROS/NetworkSetup\">ROS</a>.\n\nAfter creating `rostopic-echo.yaml`, use `kubectl` to apply it to your cluster:\n\n```shell\nkubectl apply -f rostopic-echo.yaml\n```\n\nDepending on your internet connection, it will take a minute or so to download the Docker image. Wait until you see `Running`:\n\n```console\n$ watch kubectl get pods -l app=rostopic-echo\nNAMESPACE     NAME                                        READY   STATUS      RESTARTS   AGE\ndefault       rostopic-echo-576cbf47c7-dtlc6              1/1     Running     0          1m\n```\n\nNow, publish a ROS message and check that it was received inside Kubernetes:\n\n```console\n$ rostopic pub -1 chatter std_msgs/String \"Hello, world\"\n$ kubectl logs -l app=rostopic-echo\ndata: \"Hello, world\"\n---\n```\n\nKubernetes will keep this node running until you delete the deployment:\n\n```shell\nkubectl delete -f rostopic-echo.yaml\n```\n"
  },
  {
    "path": "docs/how-to/setting-up-oauth.md",
    "content": "# Setting up OAuth for web UIs\n\nEstimated time: 5 min\n\nWhen a user loads a web UI hosted in the cloud Kubernetes cluster, the server has to authenticate them before allowing them to use the service.\nTo enable this, you'll need to set up OAuth with the Cloud Console.\nOnce you've completed these steps, you'll be able to access services with web UIs, such as [Grafana](https://grafana.com/).\n\n\nIf you haven't already, complete the [Quickstart Guide](../quickstart.md) or [Deploy Cloud Robotics Core from sources](deploy-from-sources.md) to set up your GCP project.\n\n## Create OAuth credentials\n\n1. Open the [cloud console](https://console.cloud.google.com/) and ensure that\n     your cloud project is selected in the project selector dropdown at the top.\n\n1. Configure the OAuth consent screen: [APIs & Services → Credentials → OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent).\n   * User Type: Internal\n   * Application name: My Cloud Robotics Application\n   * Support email: *your email address*\n   * Add `[PROJECT_ID].cloud.goog` to Authorized domains (where `[PROJECT_ID]` is your GCP project ID).\n   * Leave the other fields blank.\n\n1. Create an OAuth client ID: [APIs & Services → Credentials → Create credentials → OAuth client ID](https://console.cloud.google.com/apis/credentials/oauthclient).\n   * Application type: Web application\n   * Restrictions → Authorized JavaScript origins:<br/>\n   `https://www.endpoints.[PROJECT_ID].cloud.goog`\n   * Restrictions → Authorized redirect URIs: <br/>\n   `https://www.endpoints.[PROJECT_ID].cloud.goog/oauth2/callback`\n   * Click \"Create\".\n\nYou'll see a dialog containing the client ID and secret which we will add to your `config.sh` next.\n\n## Update your config and redeploy\n\n1. update your `config.sh` in the Google Cloud Storage bucket:\n    ```shell\n    curl -fS \"https://storage.googleapis.com/cloud-robotics-releases/run-install.sh\" >run-install.sh\n    bash ./run-install.sh $PROJECT_ID --set-oauth\n    ```\n    Enter the OAuth client ID and secret from the previous step when asked.\n1. Update your cloud project:\n    ```shell\n    bash ./run-install.sh $PROJECT_ID\n    ```\n\nAfter the update has been deployed, OAuth is enabled in your cloud project.\nVerify that `oauth2-proxy` is running now:\n```console\n$ kubectl get pods\n\nNAME               READY   STATUS    RESTARTS   AGE\n...\noauth2-proxy-xxx   1/1     Running   0          1m\n```\n\n## Try it out\n\nOpen a web browser and visit `https://www.endpoints.[PROJECT_ID].cloud.goog/grafana/dashboards`, replacing `[PROJECT_ID]` with your GCP project ID.\nYou'll be prompted to log in with your Google account, after which you'll see a list of dashboards.\nTry selecting \"Kubernetes Capacity Planning\" to see the resource usage of the Kubernetes cluster.\n"
  },
  {
    "path": "docs/how-to/using-cloud-storage.md",
    "content": "# Using Cloud Storage from a robot\n\nEstimated time: 20 minutes\n\nThis page describes a simple Cloud Storage transaction that demonstrates how Google Cloud APIs can be accessed without additional authentication configuration from within the robot's Kubernetes cluster.\n\nNormally, to access a private Cloud Storage bucket from a robot, you'd need to manage a service account for the robot through Identity & Access Management (IAM). Cloud Robotics handles the robot's identity for you, so you can connect securely without additional configuration.\n\n1. If you haven't already, complete the [Connecting a robot to the cloud](connecting-robot.md) steps.\n\n1. Choose a name for the Cloud Storage bucket.\n\n    In the course of this guide, the robot will upload a file into a private bucket. The bucket namespace is global, so we must take care to choose a bucket name that is not in use yet by any other user of GCP. See also the [bucket naming requirements](https://cloud.google.com/storage/docs/naming), and [best practices](https://cloud.google.com/storage/docs/best-practices#naming).\n\n    For this guide we will assume a bucket name like `robot-hello-world-dc1bb474`, where the part after the last dash is a random hexadecimal number. You can generate your own unique bucket name with the command\n\n    ```shell\n    echo robot-hello-world-$(tr -dc 'a-f0-9' < /dev/urandom | head -c8)\n    ```\n\n    Note: If the bucket name is already in use, creating the bucket in the next step will fail. In this case, choose a different bucket name.\n\n1. Create the Cloud Storage bucket.\n\n    On your workstation, run:\n\n    ```shell\n    gcloud storage buckets create gs://[BUCKET_NAME]\n    ```\n\n    Replace `[BUCKET_NAME]` with the name of the bucket you created, e.g., `robot-hello-world-dc1bb474`.\n    `gcloud storage` contains the sub-commands for accessing Cloud Storage, it is part of the `gcloud-sdk` package.\n\n    Note that the bucket is not publicly writable, as can be verified in the [Cloud Storage browser](https://console.cloud.google.com/storage/browser).\n\n1. Drop a file into the bucket from the robot.\n\n    On the robot, run:\n\n    ```console\n    docker pull python:alpine\n    kubectl run python --restart=Never --rm -ti --image=python:alpine -- /bin/sh\n    # apk add gcc musl-dev libffi-dev\n    # pip3 install google-cloud-storage\n    # python3\n    >>> from google.cloud import storage\n    >>> client = storage.Client()\n    >>> bucket = client.bucket(\"[BUCKET_NAME]\")\n    >>> bucket.blob(\"hello_world.txt\").upload_from_string(\"Hello, I am a robot!\\n\")\n    ```\n\n    Replace `[BUCKET_NAME]` with the name of the bucket you created.\n\n1. Verify that the file was uploaded.\n\n    On your workstation, run:\n\n    ```shell\n    gcloud storage cat gs://[BUCKET_NAME]/hello_world.txt\n    ```\n\n    This should result in the output `Hello, I am a robot!`.\n\nSo why was the robot able to drop a file in the non-public bucket? There is a lot going on in the background that enabled the configuration-less secure API access:\n\n* When the robot was connected to the cloud, it generated a new private key and registered the corresponding public key in a device registry, e.g., as Kubernetes configmaps.\n* The setup-robot command also started a Metadata Server as a workload in the robot's Kubernetes cluster. You can verify it is running with `kubectl get pods`. The Metadata Server identifies itself to the cloud using the robot's private key and obtains short-lived access tokens in the background.\n* Every time a client library performs a call to a Google Cloud API, it asks the local Metadata Server for an access token.\n* The permissions of the robot can be inspected and managed in the Cloud Console under \"IAM &amp; admin\"; you will notice that there is a service account called `robot-service@[PROJECT_ID].iam.gserviceaccount.com`, which has \"Storage Admin\" permissions. These permissions allowed the robot to write to the private bucket.\n\nWhat's next:\n\n* You can experiment with accessing other Google Cloud APIs, such as [Logging](https://cloud.google.com/logging/docs/) or [Pub/Sub](https://cloud.google.com/pubsub/docs/), from the robot programmatically. Also, Python is not the only programming language with Google Cloud client libraries: the APIs can be accessed, e.g., from code written in [Go](https://cloud.google.com/storage/docs/reference/libraries#client-libraries-install-go) in a similar configuration-less manner.\n* [Write your own service](deploying-service.md) that runs as a container in the cloud and provides an API that can be accessed securely from the robot.\n"
  },
  {
    "path": "docs/index.md",
    "content": "Google's Cloud Robotics Core is an open source platform that provides\ninfrastructure essential to building and running robotics solutions for business\nautomation. Cloud Robotics Core makes managing robot fleets easy for developers,\nintegrators, and operators. It enables:\n\n* packaging and distribution of applications\n* secure, bidirectional robot-cloud communication\n* easy access to Google Cloud services such as ML, logging, and monitoring.\n\n![Cloud Robotics Core overview](cloud-robotics-core-overview.png)\n\n### Documentation\n\n* [Quickstart](quickstart.md): Set up Cloud Robotics from binaries.\n* [Overview](overview.md): Develop a deeper understanding of Cloud Robotics.\n* Concepts\n    * Common: [Project configuration](concepts/config.md)\n    * Layer 1: [Federation](concepts/federation.md), \n      [Device Identity](concepts/device_identity.md)\n    * Layer 2: [Application Management](concepts/app-management.md)\n* How-to guides\n    * [Deploying Cloud Robotics from sources](how-to/deploy-from-sources)<br/>\n      Build and deploy Cloud Robotics from the sources hosted on Github using\n      Bazel.\n    * [Running a ROS node as a Kubernetes deployment](how-to/running-ros-node.md)<br/>\n      Use Kubernetes to administer containerized workloads on a robot.\n    * [Setting up OAuth for web UIs](how-to/setting-up-oauth.md)<br/>\n      Use services like Grafana with a web browser.\n    * [Connecting a robot to the cloud](how-to/connecting-robot.md)<br/>\n      Enable secure communication between a robot and the Google Cloud Platform.\n    * [Using Cloud Storage from a robot](how-to/using-cloud-storage.md)<br/>\n      Programmatically store data from the robot with Cloud Storage.\n    * [Deploying a service to the cloud](how-to/deploying-service.md)<br/>\n      Run an API service in the cloud cluster and access it from a robot.\n    * [Deploying a gRPC service](how-to/deploying-grpc-service.md)<br/>\n      Run an gRPC service written in C++ in the cloud cluster and access it from a robot.\n    * [Creating a declarative API](how-to/creating-declarative-api.md)<br/>\n      Create a Kubernetes-style declarative API and run it on the cloud or on a robot.\n* Development\n    * [Debugging authentication problems](developers/debug-auth.md)<br/>\n      Useful tips for working with Authentication and Authorization systems.\n"
  },
  {
    "path": "docs/overview.md",
    "content": "# Overview of Cloud Robotics Core\n\nTo understand Cloud Robotics Core, you should be familiar with the following concepts:\n\n**Docker containers**\n: Containers decouple applications from the environment in which they run. They let you deploy\napplications easily and consistently, regardless of whether the target environment is a robot,\nan on-premise data center, or the public cloud. Docker is a popular, open-source container format.\nRead more about [Containers at Google](https://cloud.google.com/containers/).\n\n**Kubernetes**\n: Kubernetes is an open-source system to deploy, scale, and manage containerized applications\nanywhere. It lets you deploy containerized applications onto your robots, run them on one or more\ncompute nodes and manage associated resources like configuration settings or networking. Read\nmore in the [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/).\n\n**Helm**\n: Helm is a tool for managing Kubernetes charts. Charts are packages of pre-configured Kubernetes\nresources. Read more in the [Helm documentation](https://github.com/helm/helm/blob/master/README.md).\n\n**Custom Resource (CR)**\n: Custom resources are extensions of the Kubernetes API that let you manage application-specific\ndata, offering [many features](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#common-features).\nTo use a custom resource, you first have to create a Custom Resource Definition (CRD). Read more\nabout [extending the Kubernetes API with Custom Resource Definitions](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/).\n\n## Layers\n\nCloud Robotics Core builds upon standard Kubernetes management tools and several open-source\npackages. We structure Cloud Robotics Core into several layers that address distinct needs.\n\n![Cloud Robotics Core layers](cloud-robotics-core-layers.png)\n\n### Layer 0: Kubernetes on Robot\n\nLayer 0 is an on-robot Kubernetes setup optimized for single-node clusters. It lets you deploy\ncontainerized workloads onto the robot, run them on one or more compute nodes and manage associated\nresources like configuration settings or networking without the overhead of a VM.\n\nYou can try it out by following the [Running a ROS node as a Kubernetes deployment](how-to/running-ros-node)\nHow-to Guide.\n\n### Layer 1: Robot Fleet Connectivity and Security\n\nLayer 1 provides secure communication and access control. Every robot is identified by a unique\nkeypair. The public key is managed in as Kubernetes configmap.\nA cloud-based authorization service uses these keys to authenticate robots and generate short-lived\nOAuth access tokens.\n\nThis approach follows the [BeyondCorp](https://cloud.google.com/beyondcorp/) zero trust network\nmodel: all connections are authenticated and authorized individually, without a need for a\ntraditional VPN. A specific robot's access may be revoked if needed. The same set of credentials\nalso serves as foundation for Apps to securely communicate with the cloud over gRPC.\n\nYou can try it out by following the [Connecting a robot to the cloud](how-to/connecting-robot)\nHow-to Guide.\n\nLayer 1 also provides a light-weight cluster federation system that synchronizes selected custom\nresources across the fleet. This provides developers with a pattern for command & control that is\nrobust against intermittent connectivity and fits well with the overall declarative Kubernetes\nmodel. You can read more in the concept guide about [Federation](concepts/federation.md).\n\n> **Note:** Layer 2 and 3 (below) are currently under development. You'll find an early\n> implementation of these layers in our repository but corresponding APIs and concepts are not stable yet.\n\n### Layer 2: App Management\n\nLayer 2 introduces App management, built as a lightweight facade on top of Kubernetes and the Helm\npackage manager. In Cloud Robotics Core, Apps consist of one or more Docker containers and\nassociated resources, that run on the robot and in the cloud. The App management layer determines\nwhich Apps, and app components, run on robots and in the cloud. The concept guide has more details\non [Application Management](concepts/app-management.md).\n\n### Layer 3: Managed Repositories\n\nLayer 3 adds the capability to download Apps from remote repositories. Most of our core platform\nservices will be packaged as optional downloadable extensions via a Google-managed repository. In\naddition, vendor-managed repositories can be added and used for locating new or updated vendor-\nprovided Apps.\n\nApps provide DevOps and robotics services such as:\n\n* Logs aggregation and stack traces with StackDriver\n* Metric collection, upload and dashboarding with Prometheus and Grafana\n* Sensor data transport for cloud-based analysis\n* Remote debugging using RViz over WebRTC\n* Remote administration\n"
  },
  {
    "path": "docs/quickstart.md",
    "content": "# Quickstart\n\nEstimated time: 10 min\n\nThis page describes how to set up a Google Cloud Platform (GCP) project\ncontaining the Cloud Robotics Core (CRC) components.\nIn particular, this creates a cluster with Google Kubernetes Engine and prepares\nit to accept connections from robots, which enables those robots to securely\ncommunicate with GCP.\nThe commands were tested on machines running Debian (Stretch) or Ubuntu (16.04\nand 18.04) Linux.\n\n1. In the GCP Console, go to the [Manage resources][resource-manager] page and\n   select or create a project.\n1. Make sure that [billing][modify-project] is enabled for your project.\n1. [Install the Cloud SDK][cloud-sdk]. When prompted, choose the project you created above.\n1. After installing the Cloud SDK, install the `kubectl` command-line tool:\n\n    ```shell\n    gcloud components install kubectl gke-gcloud-auth-plugin\n    ```\n\n    If you're using Cloud Shell, Debian, or Ubuntu, you may need to use apt instead:\n\n    ```shell\n    apt-get install kubectl google-cloud-sdk-gke-gcloud-auth-plugin\n    ```\n\n1. Install tools required for installation:\n\n    ```shell\n    sudo apt-get install curl tar xz-utils\n    ```\n\n## Deploy the project\n\n1. Create application default credentials, which are used to deploy the cloud project.\n\n    ```shell\n    gcloud auth application-default login\n    ```\n\n1. Create a directory for CRC installer.\n\n    ```shell\n    mkdir cloud-robotics\n    cd cloud-robotics\n    ```\n\n1. Set your GCP project ID as an environment variable.\n\n    ```shell\n    export PROJECT_ID=[YOUR_GCP_PROJECT_ID]\n    ```\n\n1. Install the latest nightly build into your GCP project by running the install script.\n    Accept the default configuration by hitting `ENTER` on all questions; you can change the settings later.\n\n    ```shell\n    curl -fS \"https://storage.googleapis.com/cloud-robotics-releases/run-install.sh\" >run-install.sh\n    bash ./run-install.sh $PROJECT_ID\n    ```\n\nThe install script created a Kubernetes cluster using Google Kubernetes Engine\nand used [Synk][synk] to install the Cloud Robotics Core component helm charts.\nYou can browse these components on the [Workloads dashboard][workloads].\nAlternatively, you can list them from the console on your workstation:\n\n```console\n$ kubectl get pods\n\nNAME                READY   STATUS             RESTARTS   AGE\ncert-manager-xxx    1/1     Running            0          1m\nnginx-ingress-xxx   1/1     Running            0          1m\noauth2-proxy-xxx    0/1     CrashLoopBackOff   4          1m\ntoken-vendor-xxx    1/1     Running            0          1m\n```\n\n> **Note** Unless you already set up OAuth, the `oauth2-proxy` will show an error which we will ignore for now.\n\nIn addition to the cluster, the install script also created:\n\n* the [[PROJECT_ID]-cloud-robotics-config bucket][storage-bucket], containing a `config.sh` and a Terraform state which are necessary to update your cloud project later,\n* the [[PROJECT_ID]-robot Cloud Storage bucket][storage-bucket], containing the scripts that connect robots to the cloud, and\n* the [Identity & Access Management policies][iam] that authorize robots and humans to communicate with GCP.\n\n## Update the project\n\nTo update your Cloud Robotics configuration, run the install script with the `--set-config` flag.\n\n```shell\nbash ./run-install.sh $PROJECT_ID --set-config\n```\n\nThis command only updates the config but does not update your cloud project.\nTo update the installation to the latest version and apply config changes, run the installer again.\n\n```shell\nbash ./run-install.sh $PROJECT_ID\n```\n\nIf you deleted the install scipt or you want to run an update from another machine which has the Cloud SDK installed, simply run:\n\n```\ncurl -fS \"https://storage.googleapis.com/cloud-robotics-releases/run-install.sh\"\\\n    | bash -s -- $PROJECT_ID\n```\n\n## Clean up\n\nThe following command will delete:\n\n* the [cloud-robotics Kubernetes cluster](https://console.cloud.google.com/kubernetes/list)\n\nThis can be useful if the cluster is in a broken state.\nBe careful with this invocation, since you'll have to redeploy the project and reconnect any robots afterwards.\n\n```shell\ncurl -fS \"https://storage.googleapis.com/cloud-robotics-releases/run-install.sh\"\\\n    | bash -s -- $PROJECT_ID --delete\n```\n\n> **Known issue** After deleting CRC from your project, the endpoint services will be in a \"pending deletion\" state for 30 days.\n> If you want to reinstall CRC into the same project again, you have to [undelete the services][undelete-service] manually.\n\nIf you want to completely shut down the project, see [the Resource Manager documentation][shutting_down_projects].\n\n## Next steps\n\n* [Connect a robot to the cloud](how-to/connecting-robot.md).\n* [Set up OAuth](how-to/setting-up-oauth.md)\n\n\n[resource-manager]: https://console.cloud.google.com/cloud-resource-manager\n[modify-project]: https://cloud.google.com/billing/docs/how-to/modify-project\n[cloud-sdk]: https://cloud.google.com/sdk/docs/\n[workloads]: https://console.cloud.google.com/kubernetes/workload\n[storage-bucket]: https://console.cloud.google.com/storage/browser\n[iam]: https://console.cloud.google.com/iam-admin/iam\n[undelete-service]: https://cloud.google.com/sdk/gcloud/reference/endpoints/services/undelete\n[shutting_down_projects]: https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects\n[synk]: https://github.com/googlecloudrobotics/core/tree/master/src/go/cmd/synk/README.md\n"
  },
  {
    "path": "new_versions.txt",
    "content": "{\n  \"cert-manager\": \"1.13.2\",\n  \"ingress-nginx\": \"1.9.4\",\n  \"oauth2-proxy\": \"7.5.1\",\n  \"stackdriver-logging-agent\": \"1.10.1\"\n}\n"
  },
  {
    "path": "non_module_deps.bzl",
    "content": "load(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n# -- load statements -- #\n\ndef _non_module_deps_impl(ctx):\n    # Sysroot and libc\n    # How to upgrade:\n    # - Find image in https://storage.googleapis.com/chrome-linux-sysroot/ for amd64 for\n    #   a stable Linux (here: Debian bullseye), of this pick a current build.\n    # - Verify the image contains expected /lib/x86_64-linux-gnu/libc* and defines correct\n    #   __GLIBC_MINOR__ in /usr/include/features.h\n    # - If system files are not found, add them in ../BUILD.sysroot\n    http_archive(\n        name = \"com_googleapis_storage_chrome_linux_amd64_sysroot\",\n        build_file = Label(\"//bazel:BUILD.sysroot\"),\n        sha256 = \"5df5be9357b425cdd70d92d4697d07e7d55d7a923f037c22dc80a78e85842d2c\",\n        urls = [\n            # features.h defines GLIBC 2.31.\n            \"https://storage.googleapis.com/chrome-linux-sysroot/toolchain/4f611ec025be98214164d4bf9fbe8843f58533f7/debian_bullseye_amd64_sysroot.tar.xz\",\n        ],\n    )\n    http_archive(\n        name = \"bazel_gomock\",\n        urls = [\n            \"https://github.com/jmhodges/bazel_gomock/archive/fde78c91cf1783cc1e33ba278922ba67a6ee2a84.tar.gz\",\n        ],\n        sha256 = \"692421b0c5e04ae4bc0bfff42fb1ce8671fe68daee2b8d8ea94657bb1fcddc0a\",\n        strip_prefix = \"bazel_gomock-fde78c91cf1783cc1e33ba278922ba67a6ee2a84\",\n    )\n    http_archive(\n        name = \"kubernetes_helm\",\n        urls = [\n            \"https://get.helm.sh/helm-v2.17.0-linux-amd64.tar.gz\",\n        ],\n        sha256 = \"f3bec3c7c55f6a9eb9e6586b8c503f370af92fe987fcbf741f37707606d70296\",\n        strip_prefix = \"linux-amd64\",\n        build_file = \"//third_party/helm2:BUILD.bazel\",\n    )\n    http_archive(\n        name = \"kubernetes_helm3\",\n        urls = [\n            \"https://get.helm.sh/helm-v3.9.0-linux-amd64.tar.gz\",\n        ],\n        sha256 = \"1484ffb0c7a608d8069470f48b88d729e88c41a1b6602f145231e8ea7b43b50a\",\n        strip_prefix = \"linux-amd64\",\n        build_file = \"//third_party/helm3:BUILD.bazel\",\n    )\n    http_archive(\n        name = \"hashicorp_terraform\",\n        urls = [\n            \"https://releases.hashicorp.com/terraform/1.11.4/terraform_1.11.4_linux_amd64.zip\",\n        ],\n        sha256 = \"1ce994251c00281d6845f0f268637ba50c0005657eb3cf096b92f753b42ef4dc\",\n        build_file = \"//third_party:terraform.BUILD\",\n    )\n    http_archive(\n        name = \"com_github_kubernetes_sigs_application\",\n        urls = [\n            \"https://github.com/kubernetes-sigs/application/archive/c8e2959e57a02b3877b394984a288f9178977d8b.tar.gz\",\n        ],\n        sha256 = \"8bafd7fb97563d1a15d9afc68c87e3aabd664f60bd8005f1ae685d79842c1ac4\",\n        strip_prefix = \"application-c8e2959e57a02b3877b394984a288f9178977d8b\",\n        build_file = \"//third_party:app_crd.BUILD\",\n    )\n    http_archive(\n        name = \"ingress-nginx\",\n        urls = [\n            \"https://github.com/kubernetes/ingress-nginx/archive/refs/tags/controller-v1.8.0.tar.gz\",\n        ],\n        sha256 = \"6e571764828b24545eea49582fd56d66d51fc66e52a375d98251c80c57fdb2fc\",\n        strip_prefix = \"ingress-nginx-controller-v1.8.0\",\n        build_file = \"//third_party:ingress-nginx.BUILD\",\n    )\n\n# -- repo definitions -- #\n\nnon_module_deps = module_extension(implementation = _non_module_deps_impl)\n"
  },
  {
    "path": "nvchecker.toml",
    "content": "[__config__]\noldver = \"current_versions.txt\"\nnewver = \"new_versions.txt\"\n\n# containers\n# git grep -E \"^\\s+image: \" *.yaml | grep -v \"{{\"\n\n[ingress-nginx]\nsource = \"container\"\n# As of 2023-06-09, nvchecker is not compatible with registry.k8s.io, which\n# doesn't return the WWW-Authenticate header that nvchecker expects, so you get\n# UnsupportedAuthenticationError.\nregistry = \"k8s.gcr.io\"\ncontainer = \"ingress-nginx/controller\"\nprefix = \"v\"\n\n[oauth2-proxy]\nsource = \"container\"\nregistry = \"quay.io\"\ncontainer = \"oauth2-proxy/oauth2-proxy\"\nprefix = \"v\"\n\n[stackdriver-logging-agent]\nsource = \"container\"\nregistry = \"gcr.io\"\ncontainer = \"stackdriver-agents/stackdriver-logging-agent\"\n\n# github packages\n\n[cert-manager]\nsource = \"github\"\ngithub = \"jetstack/cert-manager\"\nuse_latest_release = true\nprefix = \"v\"\n\n# Does find the version, try alternative at the bottom\n#[kube-prometheus-stack]\n#source = \"github\"\n#github = \"prometheus-community/helm-charts\"\n#path = \"charts/kube-prometheus-stack\"\n#use_max_tag = true\n# TODO(ensonic): requires auth token\n# use_latest_tag = true\n#prefix = \"kube-prometheus-stack\"\n\n# Cover helm repos:\n# https://medium.com/bigdatarepublic/software-versioning-on-kubernetes-806a48480832\n\n"
  },
  {
    "path": "scripts/BUILD.bazel",
    "content": "exports_files([\n    \"common.sh\",\n    \"config.sh\",\n    \"include-config.sh\",\n    \"set-config.sh\",\n])\n"
  },
  {
    "path": "scripts/backup_robots.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2021 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# see https://github.com/kubernetes/kubernetes/issues/90066#issuecomment-780236185 for hiding managed-fields\n# another tool: https://github.com/itaysk/kubectl-neat\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\n\nif ! hash jq 2>/dev/null; then\n  die \"This script needs jq (apt install jq).\"\nfi\nif ! hash yq 2>/dev/null; then\n  die \"This script needs yq (pip3 install yq).\"\nfi\n\nif [[ -z \"${CLOUD_ROBOTICS_CTX}\" ]]; then\n  die \"CLOUD_ROBOTICS_CTX needs specify the cluster context that shoudl be backed up\".\nfi\n\nkc get robots -o yaml | \\\n  yq 2>/dev/null -ry '.items[] | del(.metadata.annotations[\"kubectl.kubernetes.io/last-applied-configuration\"],.metadata.creationTimestamp,.metadata.generation,.metadata.managedFields,.metadata.resourceVersion,.metadata.selfLink,.metadata.uid,.status)' -\necho \"---\"\nkc get cm -n app-token-vendor -o yaml -l app.kubernetes.io/managed-by=token-vendor | \\\n  yq 2>/dev/null -ry '.items[] | del(.metadata.creationTimestamp,.metadata.resourceVersion,.metadata.selfLink,.metadata.uid)' -\n"
  },
  {
    "path": "scripts/check-images.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# Run with 'json' or 'text' as a first arg to select the format.\n#\n# If you don't have the API enabled run (and wait a day to get results):\n# gcloud --project ${GCP_PROJECT_ID} services enable containeranalysis.googleapis.com\n#\n# Postprocessing examples:\n# - grep \"Critical\" /tmp/cve-check.demo.txt | sed -e 's/  Critical (\\([0-9]*\\)):/\\1/g' | paste -s -d+ | bc\n#\n# Almost all of our images are built with distroless (bazel xxx_image rules). If there are\n# vulnerabilities,\n# 1.) check that we are using an up-to-date rules_docker in WORKSPACE. Check upstream\n#     for recent commits that update the distroless base images and if that does not help\n# 2.) check https://github.com/GoogleContainerTools/distroless/commits/master\n#     for fixes, if there are some, clone rules_docker, run ./update_deps.sh and sent a PR.\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\nsource \"${DIR}/include-config.sh\"\n\nfunction json {\n  need_delimiter=0\n  echo \"[\"\n  for image in $(gcloud container images list --format='csv[no-heading](name)' --repository=${CLOUD_ROBOTICS_CONTAINER_REGISTRY}); do\n    if [[ $need_delimiter == 0 ]]; then\n      need_delimiter=1\n    else\n      echo \",\"\n    fi\n    gcloud --project ${GCP_PROJECT_ID} alpha container images describe --show-package-vulnerability --format=json ${image}:${DOCKER_TAG} || \\\n      need_delimiter=0\n  done\n  echo \"]\"\n}\n\nfunction text {\n  for image in $(gcloud container images list --format='csv[no-heading](name)' --repository=${CLOUD_ROBOTICS_CONTAINER_REGISTRY}); do\n    # Filter noise\n    gcloud --project ${GCP_PROJECT_ID}  alpha container images describe --show-package-vulnerability ${image}:${DOCKER_TAG} | \\\n        egrep -v '^\\s*(registry|repository|digest):'\n  done\n}\n\nif [[ -z \"$2\" ]]; then\n  die \"Usage: $0 <project-id>\"\nfi\n\ninclude_config \"$2\"\n\n# call arguments verbatim:\n\"$@\"\n\n"
  },
  {
    "path": "scripts/common.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfunction die {\n  echo \"$1\" >&2\n  exit 1\n}\n\nfunction is_source_install {\n  # This file is present in the root folder only when installing from a binary.\n  [[ ! -e \"$(dirname \"${BASH_SOURCE[0]}\")/../INSTALL_FROM_BINARY\" ]]\n}\n\nfunction log {\n  local project\n  project=$1\n  shift\n  gcloud logging write cloud-robotics-deploy \\\n      --severity=INFO \\\n      --project=${project} \\\n      --payload-type=json \\\n      \"$(cat <<EOF\n{\n  \"message\": \"$*\",\n  \"user\": \"${USER}\",\n  \"hostname\": \"$(hostname)\",\n  \"git-branch\": \"$(git branch --show-current)\",\n  \"git-ref\": \"$(git rev-parse --short HEAD)\"\n}\nEOF\n)\"\n}\n\n# Fetch credentials for a new GKE cluster\nfunction gke_get_credentials {\n  local project\n  project=\"$1\"\n  local cluster_name\n  name=\"$2\"\n  local region\n  region=\"$3\"\n  local zone\n  zone=\"$4\"\n\n  # TODO(b/116303345): usage of 'gcloud ... get-credentials' silently switches\n  # the default context.\n  local saved_ctx\n  saved_ctx=$(kubectl config current-context 2>/dev/null) || saved_ctx=\"\"\n  trap \"[[ -n \\\"${saved_ctx}\\\" ]] && kubectl config use-context \\\"${saved_ctx}\\\"; trap - RETURN\" RETURN\n\n  local location\n  location=$(gcloud container clusters list --filter=\"name=${name}\" --format='value(location)' --project=\"${project}\")\n  case \"${location}\" in\n    ${region})\n      gcloud container clusters get-credentials \"${name}\" \\\n        --region \"${region}\" \\\n        --project \"${project}\" \\\n      ;;\n    ${zone})\n      gcloud container clusters get-credentials \"${name}\" \\\n        --zone \"${zone}\" \\\n        --project \"${project}\" \\\n      ;;\n  esac\n}\n\n# Build GKE context name for existing cluster\nfunction gke_context_name {\n  local project\n  project=\"$1\"\n  local cluster_name\n  name=\"$2\"\n  local region\n  region=\"$3\"\n  local zone\n  zone=\"$4\"\n\n  local location\n  location=$(gcloud container clusters list --filter=\"name=${name}\" --format='value(location)' --project=\"${project}\")\n  if [[ \"${location}\" == \"${zone}\" || \"${location}\" == \"${region}\" ]]; then\n    echo \"gke_${project}_${location}_${name}\"\n  fi\n}\n\nfunction kc {\n  kubectl --context=\"${CLOUD_ROBOTICS_CTX}\" \"$@\"\n}\n"
  },
  {
    "path": "scripts/config.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2024 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Escapes the input \"foo bar\" -> \"foo\\ bar\".\nfunction escape {\n  sed 's/[^a-zA-Z0-9,._+@%/-]/\\\\&/g' <<< \"$@\"\n}\n\n# Escapes the input twice \"foo bar\" -> \"foo\\\\\\ bar\"\nfunction double_escape {\n  sed 's/[^a-zA-Z0-9,._+@%/-]/\\\\\\\\\\\\&/g' <<< \"$@\"\n}\n\n# Creates a substitution pattern for sed using an unprintable char as seperator.\n# This allows the user to use any normal char in the input.\nfunction sed_pattern {\n  local regexp=\"$1\"\n  local replacement=\"$2\"\n  echo s$'\\001'${regexp}$'\\001'${replacement}$'\\001'\n}\n\n# Sets the given variable in config.sh. If $value is empty, the variable\n# assignement is commented out in config.sh.\nfunction save_variable {\n  local config_file=\"$1\"\n  local name=\"$2\"\n  local value=\"$3\"\n\n  if [[ -z \"${value}\" ]]; then\n    sed -i \"s/^\\(${name}=.*\\)$/#\\1/\" \"${config_file}\"\n  elif grep -q \"^\\(# *\\)\\{0,1\\}${name}=\" \"${config_file}\"; then\n    value=$( double_escape ${value} )\n    sed -i \"$( sed_pattern \"^\\(# *\\)\\{0,1\\}${name}=.*$\" \"${name}=${value}\" )\" \"${config_file}\"\n  else\n    value=$( escape ${value} )\n    echo >>\"${config_file}\"\n    echo \"${name}=${value}\" >>\"${config_file}\"\n  fi\n}\n"
  },
  {
    "path": "scripts/include-config.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Includes the configuration variables from a config.sh.\n\nfunction check_vars_not_empty {\n  for v in \"$@\"; do\n    [ -n \"${!v}\" ] || die \"Variable $v is not set or is empty\"\n  done\n}\n\nfunction check_var_is_one_of {\n  local var_name=\"$1\"\n  local allowed_values=\"${*:2}\"\n  local found=false\n\n  for allowed_value in ${allowed_values}; do\n    if [[ \"${!var_name}\" = \"${allowed_value}\" ]]; then\n      found=true\n    fi\n  done\n\n  if [[ \"${found}\" = false ]]; then\n    die \"Variable ${var_name} has to be one of [${allowed_values}], but was ${!var_name}\"\n  fi\n}\n\nfunction include_config {\n  local project=\"$1\"\n\n  source <(gcloud storage cat \"gs://${project}-cloud-robotics-config/config.sh\")\n\n  # Check that config defines the following set of configuration variables\n  check_vars_not_empty GCP_PROJECT_ID GCP_REGION GCP_ZONE\n\n  if is_source_install; then\n    # Keep default in sync with src/go/pkg/configutil/config-reader.go\n    CLOUD_ROBOTICS_CONTAINER_REGISTRY=${CLOUD_ROBOTICS_CONTAINER_REGISTRY:-\"gcr.io/${GCP_PROJECT_ID}\"}\n    SOURCE_CONTAINER_REGISTRY=${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\n  else\n    SOURCE_CONTAINER_REGISTRY=${SOURCE_CONTAINER_REGISTRY:-gcr.io/cloud-robotics-releases}\n  fi\n\n  CLOUD_ROBOTICS_DEPLOY_ENVIRONMENT=${CLOUD_ROBOTICS_DEPLOY_ENVIRONMENT:-GCP}\n  check_var_is_one_of CLOUD_ROBOTICS_DEPLOY_ENVIRONMENT \"GCP\" \"GCP-testing\"\n\n  GKE_CLUSTER_TYPE=${GKE_CLUSTER_TYPE:-zonal}\n  check_var_is_one_of GKE_CLUSTER_TYPE \"zonal\" \"regional\"\n\n  GKE_DATAPATH_PROVIDER=${GKE_DATAPATH_PROVIDER:-DATAPATH_PROVIDER_UNSPECIFIED}\n  check_var_is_one_of GKE_DATAPATH_PROVIDER \"DATAPATH_PROVIDER_UNSPECIFIED\" \"ADVANCED_DATAPATH\"\n}\n"
  },
  {
    "path": "scripts/migrate.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2025 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# helper functions to update from older installations\n# ./migrate.sh <migration> <project-id>\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\nsource \"${DIR}/config.sh\"\nsource \"${DIR}/include-config.sh\"\n\nset -o pipefail -o errexit\n\n# Required or terraform will fail deleting the IoT registry\nfunction cleanup_iot_devices {\n  gcloud services list --project=\"${GCP_PROJECT_ID}\" | grep -q cloudiot.googleapis.com || return\n  local iot_registry_name=\"cloud-robotics\"\n  gcloud beta iot registries list --project=\"${GCP_PROJECT_ID}\" --region=\"${GCP_REGION}\" | grep -q \"${iot_registry_name}\" || return\n  local devices\n  devices=$(gcloud beta iot devices list \\\n    --project \"${GCP_PROJECT_ID}\" \\\n    --region \"${GCP_REGION}\" \\\n    --registry \"${iot_registry_name}\" \\\n    --format='value(id)')\n  if [[ -n \"${devices}\" ]] ; then\n    echo \"Clearing IoT devices from ${iot_registry_name}\" 1>&2\n    for dev in ${devices}; do\n      gcloud beta iot devices delete \\\n        --quiet \\\n        --project \"${GCP_PROJECT_ID}\" \\\n        --region \"${GCP_REGION}\" \\\n        --registry \"${iot_registry_name}\" \\\n        ${dev}\n    done\n  fi\n}\n\nfunction cleanup_helm_data {\n  # Delete all legacy HELM resources. Do not delete the Helm charts directly, as\n  # we just want to keep the resources and have synk \"adopt\" them.\n  kc delete cm ready-for-synk 2> /dev/null || true\n  kc delete cm synk-enabled 2> /dev/null || true\n  kc -n kube-system delete deploy tiller-deploy 2> /dev/null || true\n  kc -n kube-system delete service tiller-deploy 2> /dev/null || true\n  kc -n kube-system delete cm -l OWNER=TILLER 2> /dev/null || true\n}\n\nfunction cleanup_old_cert_manager {\n  # Uninstall and cleanup older versions of cert-manager if needed\n\n  echo \"checking for old cert manager ..\"\n  kc &>/dev/null get deployments cert-manager || return 0\n  installed_ver=$(kc get deployments cert-manager -o=go-template --template='{{index .metadata.labels \"helm.sh/chart\"}}' | rev | cut -d'-' -f1 | rev | tr -d \"vV\")\n  echo \"have cert manager $installed_ver\"\n\n  if [[ \"$installed_ver\" == 0.5.* ]]; then\n    echo \"need to cleanup old version\"\n\n    # see https://docs.cert-manager.io/en/latest/tasks/upgrading/upgrading-0.5-0.6.html#upgrading-from-older-versions-using-helm\n    # and https://docs.cert-manager.io/en/latest/tasks/backup-restore-crds.html\n\n    # cleanup\n    synk_version=$(kc get resourcesets.apps.cloudrobotics.com --output=name | grep cert-manager | cut -d'/' -f2)\n    echo \"deleting resourceset ${synk_version}\"\n    ${SYNK} delete ${synk_version} -n default\n    kc delete crd \\\n      certificates.certmanager.k8s.io \\\n      issuers.certmanager.k8s.io \\\n      clusterissuers.certmanager.k8s.io\n  fi\n\n  if [[ \"$installed_ver\" == 0.8.* ]]; then\n    echo \"need to cleanup old version\"\n    # see https://cert-manager.io/docs/installation/upgrading/upgrading-0.8-0.9/\n    # and https://cert-manager.io/docs/installation/upgrading/upgrading-0.9-0.10/\n\n    # cleanup\n    kc delete deployments --namespace default \\\n      cert-manager \\\n      cert-manager-cainjector \\\n      cert-manager-webhook\n\n    kc delete -n default issuer cert-manager-webhook-ca cert-manager-webhook-selfsign\n    kc delete -n default certificate cert-manager-webhook-ca cert-manager-webhook-webhook-tls\n    kc delete apiservice v1beta1.admission.certmanager.k8s.io\n  fi\n\n  if [[ \"$installed_ver\" == 0.10.* ]]; then\n    echo \"need to cleanup old version\"\n\n    # cleanup deployments\n    kc delete deployments --namespace default \\\n      cert-manager \\\n      cert-manager-cainjector \\\n      cert-manager-webhook\n\n    echo \"Wait until cert-manager pods are deleted\"\n    kc wait pods -l app.kubernetes.io/instance=cert-manager -n default --for=delete --timeout=35s\n\n    # delete existing cert-manager resources\n    kc delete Issuers,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges --all-namespaces --all\n\n    # Delete old webhook ca and tls secrets\n    kc delete secrets --namespace default cert-manager-webhook-ca cert-manager-webhook-tls\n\n    # cleanup crds\n    kc delete crd \\\n      certificaterequests.certmanager.k8s.io \\\n      certificates.certmanager.k8s.io \\\n      challenges.certmanager.k8s.io \\\n      clusterissuers.certmanager.k8s.io \\\n      issuers.certmanager.k8s.io \\\n      orders.certmanager.k8s.io\n\n    # cleanup apiservices\n    kc delete apiservices v1beta1.webhook.certmanager.k8s.io\n  fi\n\n  # This is now installed as part of base-cloud\n  kc delete resourcesets.apps.cloudrobotics.com -l name=cert-manager 2>/dev/null || true\n}\n\n# main\nif [[ \"$#\" -lt 2 ]] || [[ ! \"$1\" =~ ^(cleanup_helm_data|cleanup_old_cert_manager|cleanup_iot_devices)$ ]]; then\n  die \"Usage: $0 {cleanup_helm_data|cleanup_old_cert_manager|cleanup_iot_devices} <project id>\"\nfi\n\ninclude_config $2\n\n# log and call arguments verbatim:\nlog $2 $0 $1\n\"$@\"\n"
  },
  {
    "path": "scripts/pre-commit",
    "content": "#!/bin/bash\n# git hook to ensure code style\n# ln -sf ../../scripts/pre-commit .git/hooks/\n\n# shellcheck disable=2044,2046\n# This script can't handle spaces in filenames. That would be challenging to do\n# correctly, and we will hopefully never add a filename with a space to the\n# repository.\n\nset -o pipefail\n\nresult=0\n\n# Allow to call the pre-commit hook with a list of files. This allows to run the\n# script from the command line like this to check all files:\n# $ ./scripts/pre-commit $(git ls-files)\nfiles=\"$*\"\nif [ -z \"$files\" ]; then\n  files=\"$(git diff --name-only --staged --diff-filter=ACMRTUXB)\"\nfi\n\nfunction files_matching {\n  local include=\"$1\"\n  local exclude=\"$2\"\n  # The diff-filter lists all but deleted files. The `echo` puts the output on\n  # one line for easier copy-pasting.\n  if [[ -z \"${exclude}\" ]]; then\n    echo $(echo \"$files\" | tr ' ' '\\n' | grep -E \"${include}\")\n  else\n    echo $(echo \"$files\" | tr ' ' '\\n' | grep -E \"${include}\" | grep -vE \"${exclude}\")\n  fi\n}\n\ngo_files=$(files_matching \"\\.go$\")\n\nif [ -n \"$go_files\" ]; then\n  # meh, gofmt does not set an exit code\n  # TODO(rodrigoq): this will break if the filenames have spaces\n  diff=$(gofmt -d -e $go_files)\n  if [ -n \"$diff\" ]; then\n    echo \"$diff\"\n    files_to_fix=$(gofmt -l $go_files)\n    echo \"To fix, run: gofmt -w $files_to_fix\"\n    result=1\n  fi\nfi\n\npy_files=$(files_matching \"\\.py$\")\n\nif [ -n \"$py_files\" ]; then\n  which >/dev/null autopep8 || (echo \"Please install autopep8\"; exit 1)\n  # TODO(rodrigoq): this will break if the filenames have spaces\n  diff=$(autopep8 -d $py_files)\n  if [ -n \"$diff\" ]; then\n    echo \"$diff\"\n    echo \"To fix, run: autopep8 -i $py_files\"\n    result=1\n  fi\nfi\n\nbuild_files=$(echo \"$files\" | tr ' ' '\\n' \\\n  | grep -E \"BUILD|WORKSPACE|[.]bzl\")\n\nif [ -n \"$build_files\" ]; then\n  which >/dev/null buildifier || (echo \"Please install buildifier\"; exit 1)\n  diff=$(buildifier -d $build_files)\n  if [ -n \"$diff\" ]; then\n    echo \"$diff\"\n    echo \"To fix, run: buildifier\" \\\n      $(echo $(buildifier -mode=check $build_files | cut -d' ' -f1))\n    result=1\n  fi\nfi\n\n# Run Gazelle if a Go or BUILD file changes. This is a heuristic, but hopefully\n# covers most cases where it is needed.\nfor workspace_dir in $(find -name WORKSPACE -printf \"%h\\n\"); do\n  bzl=\"$workspace_dir/BUILD.bazel\"\n  if [ -e \"$bzl\" ] && grep -q \"gazelle(\" \"$bzl\"; then\n    # This calls gazelle for every workspace, affected or not.\n    if [[ -n \"$go_files\" || -n \"$build_files\" ]]; then\n      diff=$(cd ${workspace_dir} && bazel run :gazelle -- -mode=diff 2>/dev/null)\n      if [ -n \"$diff\" ]; then\n        echo \"$diff\"\n        echo \"To fix:\"\n        echo \"  bazel run :gazelle\"\n        result=1\n      fi\n    fi\n  fi\ndone\n\nts_files=$(files_matching \"\\.ts$\")\n\nif [ -n \"$ts_files\" ]; then\n  which >/dev/null clang-format || (echo \"Please install clang-format\"; exit 1)\n  diff=$(diff -u <(cat $ts_files) <(clang-format $ts_files))\n  if [ -n \"$diff\" ]; then\n    echo \"$diff\"\n    echo \"To fix, run: clang-format -i $ts_files\"\n    result=1\n  fi\nfi\n\ncpp_files=$(files_matching \"\\.(h|cc)$\")\n\nif [ -n \"$cpp_files\" ]; then\n  which >/dev/null clang-format || (echo \"Please install clang-format\"; exit 1)\n  diff=$(diff -u <(cat $cpp_files) <(clang-format -style=google $cpp_files))\n  if [ -n \"$diff\" ]; then\n    echo \"$diff\"\n    echo \"To fix, run: clang-format -style=google -i $cpp_files\"\n    result=1\n  fi\nfi\n\ntf_files=$(files_matching \"\\.tf$\")\n\nif [ -n \"$tf_files\" ]; then\n  if [[ -f WORKSPACE ]] ; then\n    # cloud-robotics\n    bazel build @hashicorp_terraform//:terraform\n    TERRAFORM=\"${PWD}/bazel-out/../../../external/hashicorp_terraform/terraform\"\n  else\n    # infrastructure\n    TERRAFORM=\"/google/data/ro/teams/terraform/bin/terraform\"\n  fi\n  tf_dirs=$(dirname $tf_files | sort | uniq)\n  for tf_dir in $tf_dirs; do\n    if ! ${TERRAFORM} fmt -write=false -list=false -check=true $tf_dir; then\n      ${TERRAFORM} fmt -write=false -list=false -diff=true $tf_dir\n      echo \"To fix, run: ${TERRAFORM} fmt $tf_dir\"\n      result=1\n    fi\n  done\nfi\n\n# Run check when either a markdown file changes (it may have new embeddings) or\n# an example file changes (it may have to be embedded).\n# TODO(rodrigoq): only run if these files contain an embedmd tag.\nmd_files=$(files_matching \"\\.md$\")\n# TODO(rodrigoq): find a better way of checking if a file is embedded anywhere.\nexample_files=$(files_matching \"example\")\nif [[ -n \"$md_files\" || -n \"$example_files\" ]]; then\n  EMBEDMD=${GOPATH:-$HOME/go}/bin/embedmd\n  if [[ ! -f \"$EMBEDMD\" ]] && ! go install github.com/campoy/embedmd@latest ; then\n    echo \"ERROR: embedmd not found and couldn't be installed.\" >&2\n    result=1\n  else\n    # An unchanged .md file may still have changes in the embedded files.\n    all_md_files=$(git ls-files | grep --color=never '\\.md$')\n    diff=$($EMBEDMD -d $all_md_files)\n    if [[ $? -ne 0 ]] ; then\n      echo \"$diff\"\n      echo \"To fix, run: $EMBEDMD -w \\$(git ls-files | grep --color=never '\\\\.md$')\"\n      result=1\n    fi\n  fi\nfi\n\nSHELLCHECK_DIR=\"$HOME/.cache/cloud-robotics\"\nSHELLCHECK=\"${SHELLCHECK_DIR}/shellcheck-v0.6.0/shellcheck\"\nsh_files=$(files_matching \"(\\.sh$|pre-commit$)\" \"deployments/.*/config.sh\")\n# TODO(rodrigoq): enable shellcheck in the apps repo.\nif [[ -n \"$sh_files\" && \"$(basename \"$PWD\")\" != \"apps\" ]] ; then\n  if [[ ! -e \"$SHELLCHECK\" ]] ; then\n    mkdir -p \"$SHELLCHECK_DIR\"\n    curl -fsSL https://github.com/koalaman/shellcheck/releases/download/v0.6.0/shellcheck-v0.6.0.linux.x86_64.tar.xz \\\n      | tar -C \"$SHELLCHECK_DIR\" -xJf - || exit 1\n  fi\n  # Note: using a lower severity than `warning` is quite noisy.\n  # SC1090 complains not being able able to follow `source \"${DIR}/scripts/common.sh\"`,\n  # which is a common pattern and we don't benefit from shellcheck following the\n  # `source` statement.\n  if ! \"$SHELLCHECK\" --severity=warning -e SC1090 --external-sources $sh_files ; then\n    echo \"ERROR: shellcheck found issues. These need to be fixed manually.\" >&2\n    result=1\n  fi\nfi\n\nexit $result\n"
  },
  {
    "path": "scripts/robot-sim.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Manage simulated robots\n#\n# Simulated robots are implemented as a separate cluster, running the same\n# components like a physical robot in addition to the robot simulator.\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"${DIR}/common.sh\"\nsource \"${DIR}/include-config.sh\"\n\nset -o pipefail -o errexit\n\nfunction set_defaults {\n  local GCP_PROJECT_ID=\"$1\"\n  include_config \"${GCP_PROJECT_ID}\"\n\n  if [[ -z \"${ROBOT_LABELS}\" ]]; then\n    ROBOT_LABELS=\"simulated=true\"\n  fi\n}\n\nfunction create {\n  local GCP_PROJECT_ID=\"$1\"\n  local ROBOT_NAME=\"$2\"\n  local ROBOT_TYPE=\"${3:-mir-100}\"\n\n  set_defaults \"${GCP_PROJECT_ID}\"\n\n  local GKE_SIM_CONTEXT=\"gke_${GCP_PROJECT_ID}_${GCP_ZONE}_${ROBOT_NAME}\"\n\n  # Create cloud cluster for robot simulation unless already exists.\n  # To more accurately simulate a robot cluster, this uses the\n  # robot-service@ service account instead of enabling Workload Identity, as we\n  # don't have any on-prem/robot equivalent to that.\n  gcloud >/dev/null 2>&1 container clusters describe \"${ROBOT_NAME}\" \\\n    --zone=${GCP_ZONE} --project=${GCP_PROJECT_ID} || \\\n  gcloud container clusters create \"${ROBOT_NAME}\" \\\n    --enable-legacy-authorization \\\n    --machine-type=\"e2-standard-2\" \\\n    --num-nodes=1 \\\n    --max-nodes=2 \\\n    --enable-ip-alias \\\n    --issue-client-certificate \\\n    --no-enable-basic-auth \\\n    --metadata disable-legacy-endpoints=true \\\n    --scopes gke-default,cloud-platform \\\n    --service-account \"robot-service@${GCP_PROJECT_ID}.iam.gserviceaccount.com\" \\\n    --zone=${GCP_ZONE} \\\n    --project=${GCP_PROJECT_ID}\n\n  gke_get_credentials \"${GCP_PROJECT_ID}\" \"${ROBOT_NAME}\" \"${GCP_REGION}\" \"${GCP_ZONE}\"\n\n  POD_CIDR=$(gcloud container clusters describe \"${ROBOT_NAME}\" \\\n    --project=${GCP_PROJECT_ID} \\\n    --zone=${GCP_ZONE} \\\n    | grep podIpv4CidrBlock | awk '{print $2;}')\n\n  # shellcheck disable=2097 disable=2098\n  KUBE_CONTEXT=${GKE_SIM_CONTEXT} \\\n  HOST_HOSTNAME=\"nic0.${ROBOT_NAME}${GCP_ZONE}.c.${GCP_PROJECT_ID}.internal.gcpnode.com\" \\\n  ACCESS_TOKEN=$(gcloud auth application-default print-access-token) \\\n    $DIR/../src/bootstrap/robot/setup_robot.sh \\\n    ${ROBOT_NAME} \\\n    --project ${GCP_PROJECT_ID} \\\n    --robot-type \"${ROBOT_TYPE}\" \\\n    --fluentd=false \\\n    --fluentbit=false \\\n    --running-on-gke=true \\\n    --pod-cidr \"${POD_CIDR}\" \\\n    --labels \"${ROBOT_LABELS}\"\n}\n\nfunction delete {\n  local GCP_PROJECT_ID=\"$1\"\n  local ROBOT_NAME=\"$2\"\n\n  set_defaults \"${GCP_PROJECT_ID}\"\n\n  kubectl --context=${CLOUD_ROBOTICS_CTX} delete robots.registry.cloudrobotics.com \"${ROBOT_NAME}\" || true\n  gcloud container clusters delete \"${ROBOT_NAME}\" \\\n    --zone=${GCP_ZONE} --project=${GCP_PROJECT_ID}\n}\n\n# Alias for create.\nfunction update {\n  create \"$@\"\n}\n\n# main\n\nif [[ \"$#\" -lt 3 ]]; then\n  die \"Usage: $0 {create|delete|update} <project-id> <robot-name> [<robot-type>]\"\nfi\n\n# call arguments verbatim:\n\"$@\"\n"
  },
  {
    "path": "scripts/set-config.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# TODO(skopecki)\n#     * Consider setting CLOUD_ROBOTICS_SHARED_OWNER_GROUP and\n#       APP_MANAGEMENT as well.\n\nset -o pipefail -o errexit\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\" && pwd )\"\n\nsource \"${DIR}/scripts/common.sh\"\nsource \"${DIR}/scripts/config.sh\"\n\n# Reads a variable from user input.\nfunction read_variable {\n  local target_var=\"$1\"\n  local question=\"$2\"\n  local default=\"$3\"\n\n  echo\n  echo \"${question}\"\n  if [[ -n \"${default}\" ]]; then\n    echo -n \"[ENTER] for \\\"${default}\\\": \"\n  fi\n  read -er input\n\n  if [[ -z \"${input}\" ]]; then\n    # shellcheck disable=SC2046\n    eval ${target_var}=$( escape ${default} )\n  else\n    # shellcheck disable=SC2046\n    eval ${target_var}=$( escape ${input} )\n  fi\n}\n\n# Outputs the variable to the user.\nfunction print_variable {\n  local description=\"$1\"\n  local value=\"$2\"\n\n  if [[ -n \"${value}\" ]]; then\n    echo \"${description}: ${value}\"\n  fi\n}\n\n# Asks a yes/no question and returns the mapped input.\nfunction ask_yn {\n  local question=\"$1\"\n  local default=\"$2\"\n\n  echo\n  echo -n \"$question\"\n  if [[ \"${default}\" = \"n\" ]]; then\n    echo -n \" [yN] \"\n  else\n    echo -n \" [Yn] \"\n  fi\n\n  while true; do\n    read -n 1 input\n    if [[ -z \"${input}\" ]]; then\n      if [[ \"${default}\" = \"n\" ]]; then\n        return 1\n      else\n        return 0\n      fi\n    fi\n    echo\n    if [[ \"${input}\" =~ y|Y ]]; then\n      return 0\n    elif [[ \"${input}\" =~ n|N ]]; then\n      return 1\n    fi\n    echo -n \"Please answer with 'y' or 'n'. \"\n  done\n}\n\n# Parse flags.\nif [[ ! \"$1\" = --* ]]; then\n  GCP_PROJECT_ID=\"$1\"\nfi\n\nfor arg in \"$@\"; do\n  if [[ \"${arg}\" = \"--ensure-config\" ]]; then\n    FLAG_ENSURE_CONFIG=1\n  elif [[ \"${arg}\" = \"--edit-oauth\" ]]; then\n    FLAG_EDIT_OAUTH=1\n  fi\ndone\n\nif [[ -z \"${GCP_PROJECT_ID}\" ]]; then\n  echo\n  echo \"Usage: $0 <project id> [<options>]\"\n  echo \"Supported options:\"\n  echo \"  --ensure-config    Does nothing if a config exists already.\"\n  echo \"  --edit-oauth       Enables and configures OAuth.\"\n  die\nfi\n\n# Load config if it exists.\nCLOUD_BUCKET=\"gs://${GCP_PROJECT_ID}-cloud-robotics-config\"\n\nCONFIG_FILE=\"$(mktemp)\"\ntrap '{ rm -f ${CONFIG_FILE}; }' EXIT\n\nif gcloud storage cp \"${CLOUD_BUCKET}/config.sh\" \"${CONFIG_FILE}\" 2>/dev/null; then\n  if [[ -n \"${FLAG_ENSURE_CONFIG}\" ]]; then\n    echo \"Found Cloud Robotics config.\"\n    exit 0\n  fi\n  source ${CONFIG_FILE}\nelse\n  if [[ -n \"${FLAG_EDIT_OAUTH}\" ]]; then\n    die \"You have to create a config before you can enable OAuth.\"\n  fi\n  cp ${DIR}/config.sh.tmpl ${CONFIG_FILE}\nfi\n\n# Check that the project exists and we have access.\ngcloud projects describe \"${GCP_PROJECT_ID}\" >/dev/null \\\n  || die \"ERROR: unable to access Google Cloud project: ${GCP_PROJECT_ID}\"\n\nfunction set_default_vars {\n  # Enable Compute Engine API which is necessary to validate the zones.\n  if ! gcloud services list --enabled --project ${GCP_PROJECT_ID} \\\n        | grep \"^compute.googleapis.com \\+\" >/dev/null; then\n    # TODO(skopecki) This can take a minute. Find a better solution to verify compute zones.\n    echo \"Enabling Compute Engine API...\"\n    gcloud services enable compute.googleapis.com --project ${GCP_PROJECT_ID}\n  fi\n\n  # Ask for region and zone.\n  GCP_ZONE=${GCP_ZONE:-\"europe-west1-c\"}\n  read_variable GCP_ZONE \"In which zone should Cloud Robotics be deployed?\" \"${GCP_ZONE}\"\n\n  # Verify the zone exists.\n  gcloud compute zones list -q --project \"${GCP_PROJECT_ID}\" --uri | grep -q \"zones/${GCP_ZONE}$\" \\\n    || die \"ERROR: the zone does not exist in your project: ${GCP_ZONE}\"\n\n  GCP_REGION=${GCP_ZONE%-?}\n\n  # Ask for gke cluster type\n  GKE_CLUSTER_TYPE=\"zonal\"\n  while :; do\n    read_variable GKE_CLUSTER_TYPE \"Should the cluster be 'zonal' or 'regional'?\" \"${GKE_CLUSTER_TYPE}\"\n\n    if [[ \"${GKE_CLUSTER_TYPE}\" == \"zonal\" || \"${GKE_CLUSTER_TYPE}\" == \"regional\" ]]; then\n      break\n    fi\n    echo \"Value must be one of: 'zonal','regional'\"\n  done\n\n  # Use dataplane_v2 for all new projects\n  GKE_DATAPATH_PROVIDER=\"ADVANCED_DATAPATH\"\n\n  # Ask for Terraform bucket and location.\n  OLD_TERRAFORM_GCS_BUCKET=\"${TERRAFORM_GCS_BUCKET}\"\n  OLD_TERRAFORM_GCS_PREFIX=\"${TERRAFORM_GCS_PREFIX}\"\n  if [[ -z \"${TERRAFORM_GCS_BUCKET}\" ]]; then\n    OLD_TF_LOCATION=\"${CLOUD_BUCKET}/terraform\"\n  else\n    OLD_TF_LOCATION=\"gs://${TERRAFORM_GCS_BUCKET}/${TERRAFORM_GCS_PREFIX}\"\n  fi\n  read_variable TF_LOCATION \"In which GCP storage folder should your Terraform state be stored?\" \\\n    \"${OLD_TF_LOCATION}\"\n  TF_LOCATION_REGEX='^\\(gs://\\)\\?\\([-_a-zA-Z0-9]\\+\\)\\(\\/\\(.*[^/]\\)\\)\\?/\\?$'\n  TERRAFORM_GCS_BUCKET=$( echo \"${TF_LOCATION}\" | sed \"s#${TF_LOCATION_REGEX}#\\2#;q\" ) \\\n    || die \"ERROR: Invalid GCP storage folder. Accepted format: gs://<bucket>/<folder>\"\n  TERRAFORM_GCS_PREFIX=$( echo \"${TF_LOCATION}\" | sed \"s#${TF_LOCATION_REGEX}#\\4#;q\" )\n\n  # Docker registries.\n  if is_source_install; then\n    read_variable CLOUD_ROBOTICS_CONTAINER_REGISTRY \\\n      \"Which Docker registry do you want to use when installing from sources? Use \\\"default\\\" for gcr.io/${GCP_PROJECT_ID}.\" \\\n      \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY:-default}\"\n    if [[ \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\" == \"default\" ]]; then\n      CLOUD_ROBOTICS_CONTAINER_REGISTRY=\n    fi\n  fi\n\n  # TODO(skopecki) If CLOUD_ROBOTICS_CONTAINER_REGISTRY is private and does not belongs to this GCR project,\n  #     it could be added automatically to PRIVATE_DOCKER_PROJECTS.\n  read_variable PRIVATE_DOCKER_PROJECTS \\\n    \"Do you need to read private Docker images from a GCR project? Space-separated list of alphanumeric project ids, or \\\"none\\\" for none.\" \\\n    \"${PRIVATE_DOCKER_PROJECTS:-none}\"\n  if [[ \"${PRIVATE_DOCKER_PROJECTS}\" == \"none\" ]]; then\n    PRIVATE_DOCKER_PROJECTS=\n  fi\n\n  # Certificate provider\n  set_certificate_provider_vars\n}\n\nfunction set_oauth_vars {\n  echo \"Follow https://googlecloudrobotics.github.io/core/how-to/setting-up-oauth.html to obtain OAuth client id and secret.\"\n\n  read_variable CLOUD_ROBOTICS_OAUTH2_CLIENT_ID \"Enter OAuth client id.\" \\\n    \"${CLOUD_ROBOTICS_OAUTH2_CLIENT_ID}\"\n\n  read_variable CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET \"Enter OAuth client secret.\" \\\n    \"${CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET}\"\n\n  if [[ -z \"${CLOUD_ROBOTICS_COOKIE_SECRET}\" ]] ||\\\n      ask_yn \"Generate new cookie secret?\" \"n\"; then\n    CLOUD_ROBOTICS_COOKIE_SECRET=\"$( head -c 16 /dev/urandom | base64 )\"\n  fi\n}\n\nfunction set_certificate_provider_vars {\n  CA_OPTIONS=\"lets-encrypt, google-cas\"\n  CA_DEFAULT=\"lets-encrypt\"\n\n  # Select provider\n  read_variable CLOUD_ROBOTICS_CERTIFICATE_PROVIDER \\\n    \"Select the certificate provider. Should be one of: ${CA_OPTIONS}.\" \\\n    \"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER:-${CA_DEFAULT}}\"\n\n  # Request certificate configuration if the provider requires it\n  if [[ ! \"lets-encrypt\" =~ (\" \"|^)\"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}\"(\" \"|$) ]]; then\n    set_certificate_vars\n  fi\n}\n\nfunction set_certificate_vars {\n  echo \"Configuring certificate information.\"\n  echo \"Refer to RFC 4519 for explanations of the fields: https://datatracker.ietf.org/doc/html/rfc4519#section-2\"\n\n  read_variable CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION \\\n    \"Organization (O)\" \\\n    \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION:-${GCP_PROJECT_ID}}\"\n\n  read_variable CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME \\\n    \"Common Name (CN)\" \\\n    \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME:-${GCP_PROJECT_ID}}\"\n\n  read_variable CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT \\\n    \"(Optional) Organizational Unit (OU)\" \\\n    \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}\"\n}\n\nif [[ -n \"${FLAG_EDIT_OAUTH}\" ]]; then\n  set_oauth_vars\nelse\n  set_default_vars\nfi\n\n# Output configuration before saving.\necho\necho \"   Your configuration\"\necho \"========================\"\nprint_variable \"GCP project ID\" \"${GCP_PROJECT_ID}\"\nprint_variable \"GCP region\" \"${GCP_REGION}\"\nprint_variable \"GCP zone\" \"${GCP_ZONE}\"\nprint_variable \"GKE cluster type\" \"${GKE_CLUSTER_TYPE}\"\nprint_variable \"GKE datapath provider\" \"${GKE_DATAPATH_PROVIDER}\"\nprint_variable \"Terraform state bucket\" \"${TERRAFORM_GCS_BUCKET}\"\nprint_variable \"Terraform state directory\" \"${TERRAFORM_GCS_PREFIX}\"\nprint_variable \"Docker container registry\" \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\"\nprint_variable \"Projects for private Docker images\" \"${PRIVATE_DOCKER_PROJECTS}\"\nprint_variable \"OAuth client id\" \"${CLOUD_ROBOTICS_OAUTH2_CLIENT_ID}\"\nprint_variable \"OAuth client secret\" \"${CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET}\"\nprint_variable \"OAuth cookie secret\" \"${CLOUD_ROBOTICS_COOKIE_SECRET}\"\nprint_variable \"Certificate provider\" \"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}\"\nprint_variable \"Certificate Subject Organization (O)\" \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION}\"\nprint_variable \"Certificate Subject Common Name (CN)\" \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME}\"\nprint_variable \"Certificate Subject Organizational Unit (OU)\" \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}\"\n\nif ! ask_yn \"Would you like to save this configuration?\"; then\n  exit 0\nfi\n\nif [[ -n \"${OLD_TERRAFORM_GCS_BUCKET}\" &&\\\n      ( ! \"${OLD_TERRAFORM_GCS_BUCKET}\" = \"${TERRAFORM_GCS_BUCKET}\" ||\\\n        ! \"${OLD_TERRAFORM_GCS_PREFIX}\" = \"${TERRAFORM_GCS_PREFIX}\") ]]; then\n  # Copy Terraform state to new location.\n  echo \"Copying Terraform state...\"\n  gcloud storage cp \"gs://${OLD_TERRAFORM_GCS_BUCKET}/${OLD_TERRAFORM_GCS_PREFIX}/*.tfstate\" \\\n    \"gs://${TERRAFORM_GCS_BUCKET}/${TERRAFORM_GCS_PREFIX}/\"\nfi\n\n# Save all parameter values.\necho\necho \"Saving configuration...\"\nsave_variable \"${CONFIG_FILE}\" GCP_PROJECT_ID \"${GCP_PROJECT_ID}\"\nsave_variable \"${CONFIG_FILE}\" GCP_REGION \"${GCP_REGION}\"\nsave_variable \"${CONFIG_FILE}\" GCP_ZONE \"${GCP_ZONE}\"\nif [[ \"${GKE_CLUSTER_TYPE}\" == \"regional\" ]]; then\n  save_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CTX \"gke_${GCP_PROJECT_ID}_${GCP_REGION}_cloud-robotics\"\nelse\n  save_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CTX \"gke_${GCP_PROJECT_ID}_${GCP_ZONE}_cloud-robotics\"\nfi\nsave_variable \"${CONFIG_FILE}\" GKE_CLUSTER_TYPE \"${GKE_CLUSTER_TYPE}\"\nsave_variable \"${CONFIG_FILE}\" GKE_DATAPATH_PROVIDER \"${GKE_DATAPATH_PROVIDER}\"\nsave_variable \"${CONFIG_FILE}\" TERRAFORM_GCS_BUCKET \"${TERRAFORM_GCS_BUCKET}\"\nsave_variable \"${CONFIG_FILE}\" TERRAFORM_GCS_PREFIX \"${TERRAFORM_GCS_PREFIX}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CONTAINER_REGISTRY \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\"\nsave_variable \"${CONFIG_FILE}\" PRIVATE_DOCKER_PROJECTS \"${PRIVATE_DOCKER_PROJECTS}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_OAUTH2_CLIENT_ID \"${CLOUD_ROBOTICS_OAUTH2_CLIENT_ID}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET \"${CLOUD_ROBOTICS_OAUTH2_CLIENT_SECRET}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_COOKIE_SECRET \"${CLOUD_ROBOTICS_COOKIE_SECRET}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CERTIFICATE_PROVIDER \"${CLOUD_ROBOTICS_CERTIFICATE_PROVIDER}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATION}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_COMMON_NAME}\"\nsave_variable \"${CONFIG_FILE}\" CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT \"${CLOUD_ROBOTICS_CERTIFICATE_SUBJECT_ORGANIZATIONAL_UNIT}\"\n\n# Upload config to the cloud.\nif ! gcloud -q storage buckets describe --project ${GCP_PROJECT_ID} \"${CLOUD_BUCKET}\" >/dev/null 2>&1; then\n  gcloud storage buckets create --project ${GCP_PROJECT_ID} ${CLOUD_BUCKET}\nfi\ngcloud storage mv \"${CONFIG_FILE}\" \"${CLOUD_BUCKET}/config.sh\"\n"
  },
  {
    "path": "src/.gitignore",
    "content": ".gopath\n"
  },
  {
    "path": "src/BUILD.bazel",
    "content": "exports_files(\n    srcs = [\"go.mod\"],\n)\n"
  },
  {
    "path": "src/README.md",
    "content": "# Source Code\n\nThis directory contains the source code for Cloud Robotics Core components. Most\ncomponents are written in Go.\n\n## Go\n\nThe [Gazelle](https://github.com/bazelbuild/bazel-gazelle) tool manages bazel\nBUILD files for Go.\n\n### Dependencies\n\nTo automatically update dependencies in bazel BUILD files run:\n\n```\nbazel run //:gazelle\n```\n\nTo re-generate Go modules dependencies run this from the top-level source\ndirectory:\n\n```\n./src/gomod.sh\n```\n\nThis will always download the latest stable tag of a go module. To use a\nspecific version run eg::\n\n```\ncd src\n# use an older version, that the latest stable\ngo get -d github.com/mitchellh/go-server-timing@v1.0.1\n# use a specific, yet untagged version\ngo get -d github.com/mitchellh/go-server-timing@feb680ab92c20d57c527399b842e1941bde888c3\n# to also upgrade dependencies, use:\ngo get -d -u github.com/mitchellh/go-server-timing@v1.0.1\n```\n\nMore tips on this [one-pager](https://encore.dev/guide/go.mod)\n\n### Licenses\n\nInstall go-license:\n\n```\ngo install github.com/google/go-licenses@latest\n```\n\nrun it:\n\n```\ncd src\n# get a vsc of all licenses\n~/go/bin/go-licenses csv .\n# check for bad (forbidden) licenses, should be empty\n~/go/bin/go-licenses check ./...\n```\n\n### Docs\n\nIn order to force a new snapshot, run\n```bash\nVERSION=$(curl -s https://proxy.golang.org/github.com/googlecloudrobotics/core/@latest | jq -r \".Version\")\necho \"https://pkg.go.dev/github.com/googlecloudrobotics/core/src/go@${VERSION}\"\n```\nand open the printed link. Then that version is part of the history.\n\n## third party\n\nWe track some external deps through [nvchecker](https://github.com/lilydjwg/nvchecker).\nGet the tool by running:\n```shell\npip3 install nvchecker\n```\n\nBelow are sample commands for the common workflows. Run all those from the root\nof the repo.\n\nAdd new dependency by adding a blob to nvchecker.toml:\n```toml\n[ingress-nginx]\nsource = \"container\"\nregistry = \"k8s.gcr.io\"\ncontainer = \"ingress-nginx/controller\"\nprefix = \"v\"\n```\nGet initial version (use same command to update the version):\n```shell\n$ nvtake -c nvchecker.toml ingress-nginx=0.44.0\n```\n\nAfter updating, please also manually keep METADATA file in sync.\n\nCheck for updates:\n```shell\n$ nvchecker -c nvchecker.toml\n[I 09-06 12:26:20.253 core:354] ingress-nginx: updated from 0.44.0 to 1.0.0\n```\n\n"
  },
  {
    "path": "src/app_charts/BUILD.bazel",
    "content": "load(\"//bazel/build_rules/app_chart:cache_gcr_credentials.bzl\", \"cache_gcr_credentials\")\nload(\"//bazel/build_rules/app_chart:run_parallel.bzl\", \"run_parallel\")\n\n# base is not in this list because it's not an app, but installed\n# manually.\nAPPS = [\n    \"k8s-relay\",\n    \"mission-crd\",\n    \"prometheus\",\n    \"token-vendor\",\n    \"akri\",\n]\n\nrun_parallel(\n    name = \"push-cached-credentials\",\n    targets = [\n        \"//src/app_charts/base:base-cloud.push\",\n        \"//src/app_charts/base:base-robot.push\",\n        \"//src/app_charts/platform-apps:platform-apps-cloud.push\",\n    ] + [\n        \"//src/app_charts/{app}:{app}.push\".format(app = a)\n        for a in APPS\n    ],\n)\n\ncache_gcr_credentials(\n    name = \"push\",\n    target = \"push-cached-credentials\",\n)\n\nfilegroup(\n    name = \"app_resources\",\n    srcs = [\"//src/app_charts/{app}:{app}.yaml\".format(app = a) for a in APPS],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/README.md",
    "content": "# Testing instructions\n\nUse `bazel run :push` on the app directory to build & upload Docker images,\nupload Helm charts and update the app manifest:\n\n```bash\nbazel run //src/app_charts/k8s-relay:k8s-relay.push <registry-location>\n```\n\n# Apps\n\n## Mission CRD\n\nThe Mission CRD App creates the mission custom resource definition in the cloud and on the robot. The mission custom resources are used to send commands to the robot and are actuated by a robot-type-specific controller.\n"
  },
  {
    "path": "src/app_charts/akri/BUILD.bazel",
    "content": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\nload(\"//bazel:build_rules/helm_template.bzl\", \"helm_template\")\n\nhelm_template(\n    name = \"akri-chart.robot\",\n    chart = \"//third_party/akri:akri-0.12.9.tgz\",\n    helm_version = 3,\n    # The namespace will later be replaced with the actual one.\n    namespace = \"HELM-NAMESPACE\",\n    release_name = \"akri\",\n    values = \"akri-robot.values.yaml\",\n)\n\napp_chart(\n    name = \"akri-robot\",\n    extra_templates = [\n        \"//third_party/akri:akri-configuration-crd.yaml\",\n        \"//third_party/akri:akri-instance-crd.yaml\",\n    ],\n    files = [\n        \":akri-chart.robot\",\n    ],\n    values = \"values-robot.yaml\",\n)\n\napp(\n    name = \"akri\",\n    charts = [\n        \":akri-robot\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/akri/akri-robot.values.yaml",
    "content": "# kubernetesDistro describes the Kubernetes distro Akri is running on. It is used to conditionally set\n# distribution specific values such as container runtime socket. Options: microk8s | k3s | k8s\nkubernetesDistro: k8s\n\n# enable udev support for usb devices\nudev:\n  discovery:\n    enabled: true\n  configuration:\n    enabled: true\n    name: akri-udev\n    discoveryDetails:\n      udevRules: ${UDEV_RULES}\n"
  },
  {
    "path": "src/app_charts/akri/robot/akri.yaml",
    "content": "# This includes all resources expanded from the akri chart using\n# the values in ../values.yaml.\n# Some pseudo-variables that were inserted there are replaced with actual runtime values.\n{{ .Files.Get \"files/akri-chart.robot.yaml\" | replace \"HELM-NAMESPACE\" .Release.Namespace | replace \"${UDEV_RULES}\" (toJson .Values.udev.rules) }}\n"
  },
  {
    "path": "src/app_charts/akri/values-robot.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\n\nudev:\n  rules: []\n"
  },
  {
    "path": "src/app_charts/base/BUILD.bazel",
    "content": "load(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\nload(\"//bazel:build_rules/helm_template.bzl\", \"helm_template\")\n\n# Tests\n\napp_chart(\n    name = \"base-test\",\n    extra_templates = [\n        \":cloud/namespace.yaml\",\n        \":cloud/apps-crd.yaml\",\n        \":robot/app-management.yaml\",\n        \":robot/cert-manager.yaml\",\n        \":robot/cert-manager-certificates.yaml\",\n        \":robot/cert-manager-issuers.yaml\",\n    ],\n    files = [\n        \":cert-manager-chart.robot\",\n    ],\n    images = {\n        \"chart-assignment-controller\": \"//src/go/cmd/chart-assignment-controller:chart-assignment-controller-image\",\n    },\n    values = \":values-robot.yaml\",\n    visibility = [\"//visibility:public\"],\n)\n\nsh_test(\n    name = \"app_management_test\",\n    srcs = [\"app_management_test.sh\"],\n    data = [\n        \":base-cloud\",\n        \":base-robot\",\n        \"@kubernetes_helm//:helm\",\n    ],\n)\n\n# Robot\n\nhelm_template(\n    name = \"cert-manager-chart.robot\",\n    chart = \"//third_party/cert-manager:cert-manager-v1.16.3.tgz\",\n    helm_version = 3,\n    # The namespace will later be replaced with the actual one.\n    namespace = \"HELM-NAMESPACE\",\n    release_name = \"cert-manager\",\n    values = \"cert-manager-robot.values.yaml\",\n)\n\napp_chart(\n    name = \"base-robot\",\n    extra_templates = [\n        \":cloud/namespace.yaml\",\n        \":cloud/registry-crd.yaml\",\n        \":cloud/apps-crd.yaml\",\n        \"//third_party/kube-prometheus-stack:01-crds.yaml\",\n    ],\n    files = [\n        \":cert-manager-chart.robot\",\n        \"//third_party/fluentd_gcp_addon\",\n    ],\n    images = {\n        \"cr-syncer\": \"//src/go/cmd/cr-syncer:cr-syncer-image\",\n        \"gcr-credential-refresher\": \"//src/go/cmd/gcr-credential-refresher:gcr-credential-refresher-image\",\n        \"metadata-server\": \"//src/go/cmd/metadata-server:metadata-server-image\",\n        \"chart-assignment-controller\": \"//src/go/cmd/chart-assignment-controller:chart-assignment-controller-image\",\n    },\n    values = \"values-robot.yaml\",\n    visibility = [\"//visibility:public\"],\n)\n\n# Cloud\n\nhelm_template(\n    name = \"cert-manager-chart.cloud\",\n    chart = \"//third_party/cert-manager:cert-manager-v1.16.3.tgz\",\n    helm_version = 3,\n    # The namespace will later be replaced with the actual one.\n    namespace = \"HELM-NAMESPACE\",\n    release_name = \"cert-manager\",\n    values = \"cert-manager-cloud.values.yaml\",\n)\n\nhelm_template(\n    name = \"cert-manager-google-cas-issuer-chart.cloud\",\n    chart = \"//third_party/cert-manager-google-cas-issuer:cert-manager-google-cas-issuer-v0.6.2.tgz\",\n    # The namespace will later be replaced with the actual one.\n    namespace = \"HELM-NAMESPACE\",\n    release_name = \"cert-manager-google-cas-issuer\",\n    values = \"cert-manager-google-cas-issuer-cloud.values.yaml\",\n)\n\napp_chart(\n    name = \"base-cloud\",\n    extra_templates = [\n        \"@com_github_kubernetes_sigs_application//:app_crd\",\n        \"//third_party/kube-prometheus-stack:01-crds.yaml\",\n    ],\n    files = [\n        \"relay-dashboard.json\",\n        \":cert-manager-chart.cloud\",\n        \":cert-manager-google-cas-issuer-chart.cloud\",\n        \"@ingress-nginx//:ingress-nginx-dashboards\",\n    ],\n    images = {\n        \"app-rollout-controller\": \"//src/go/cmd/app-rollout-controller:app-rollout-controller-image\",\n        \"chart-assignment-controller\": \"//src/go/cmd/chart-assignment-controller:chart-assignment-controller-image\",\n        \"cr-syncer-auth-webhook\": \"//src/go/cmd/cr-syncer-auth-webhook:cr-syncer-auth-webhook-image\",\n    },\n    values = \"values-cloud.yaml\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/base/README.md",
    "content": "# Making changes to the `fluent-bit` configmap\n\nIf you want to change the `fluent-bit` spec, do not edit the autogenerated file\n[`./robot/fluent-bit.yaml`](./robot/fluent-bit.yaml)!\nInstead, edit [`./fluent-bit-values.yaml`](./fluent-bit-values.yaml) and run\n[`./fluent-bit-helm.sh`](./fluent-bit-helm.sh) afterwards.\n"
  },
  {
    "path": "src/app_charts/base/app_management_test.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nHELM=\"${TEST_SRCDIR}/+non_module_deps+kubernetes_helm/helm\"\nif [[ ! -x \"${HELM}\" ]] ; then\n  # If we hit this again, consider using the runfiles library:\n  # https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash#L55-L86\n  echo >&2 \"Failed to locate helm in ${TEST_SRCDIR}.\"\n  exit 1\nfi\n\nCLOUD_BASE=\"${TEST_SRCDIR}/_main/src/app_charts/base/base-cloud-0.0.1.tgz\"\nROBOT_BASE=\"${TEST_SRCDIR}/_main/src/app_charts/base/base-robot-0.0.1.tgz\"\n\nfunction test_failed() {\n  echo \"TEST FAILED: $1\"\n  exit 1\n}\n\nfunction test_passed() {\n  echo \"TEST PASSED: $1\"\n}\n\nfunction expect_app_installed() {\n  local command=\"$1\"\n  local application=\"$2\"\n  local template\n  if ! template=$(${command}); then\n    test_failed \"\\\"${command}\\\" failed\"\n  fi\n  if [[ \"${template}\" != *\"app: ${application}\"* ]]; then\n    echo \"TEMPLATE: ${template}\"\n    test_failed \"expected \\\"${application}\\\" to be installed in template created by \\\"${command}\\\"\"\n  fi\n  test_passed \"application \\\"${application}\\\" is included in template created by \\\"${command}\\\"\"\n}\n\nfunction expect_app_not_installed() {\n  local command=\"$1\"\n  local application=\"$2\"\n  local template\n  if ! template=$(${command}); then\n    echo \"TEMPLATE: ${template}\"\n    test_failed \"\\\"${command}\\\" failed\"\n  fi\n  if [[ \"${template}\" == *\"app: ${application}\"* ]]; then\n    test_failed \"did not expected \\\"${application}\\\" to be installed in template created by \\\"${command}\\\"\"\n  fi\n  test_passed \"application \\\"${application}\\\" is not included in template created by \\\"${command}\\\"\"\n}\n\nexpect_app_installed \"${HELM} template ${CLOUD_BASE} --set-string app_management=true\" \"app-rollout-controller\"\nexpect_app_installed \"${HELM} template ${CLOUD_BASE} --set-string app_management=true\" \"chart-assignment-controller\"\nexpect_app_not_installed \"${HELM} template ${CLOUD_BASE} --set-string app_management=false\" \"app-rollout-controller\"\nexpect_app_not_installed \"${HELM} template ${CLOUD_BASE} --set-string app_management=false\" \"chart-assignment-controller\"\n\nexpect_app_installed \"${HELM} template ${ROBOT_BASE} --set-string app_management=true\" \"chart-assignment-controller\"\nexpect_app_not_installed \"${HELM} template ${ROBOT_BASE} --set-string app_management=false\" \"chart-assignment-controller\"\n"
  },
  {
    "path": "src/app_charts/base/cert-manager-cloud.values.yaml",
    "content": "# Configuration for the cert-manager chart.\n# Reference: https://github.com/jetstack/cert-manager/blob/master/deploy/charts/cert-manager/values.yaml\n\n# Install CRDs by helm chart that webhook works in different namespace as cert-manager\ninstallCRDs: true\n\n# Enable Workload Identity for DNS01 support when we have a custom domain.\nserviceAccount:\n  annotations:\n    iam.gke.io/gcp-service-account: cert-manager@PROJECT-ID.iam.gserviceaccount.com\n"
  },
  {
    "path": "src/app_charts/base/cert-manager-google-cas-issuer-cloud.values.yaml",
    "content": "# Configuration for the cert-manager chart.\n# Reference: https://github.com/jetstack/google-cas-issuer/blob/main/deploy/charts/google-cas-issuer/values.yaml\n\n# No values are required for now.\n# This was put in place to add values in the future without requiring additional configuration in other files.\n# If values are added this disclaimer should be removed.\n\n# The Kubernetes service account must be annotated in order to impersonate a GCP service account using workload identity.\nserviceAccount:\n  annotations:\n    # PROJECT-ID will be replaced by a script in a future step with the contents of the `PROJECT_ID` env var.\n    iam.gke.io/gcp-service-account: sa-google-cas-issuer@PROJECT-ID.iam.gserviceaccount.com\n\napp:\n  approval:\n    subjects:\n    - kind: ServiceAccount\n      name: cert-manager\n      # TODO(alejoasd): this should be set from configuration dynamically\n      namespace: default\n"
  },
  {
    "path": "src/app_charts/base/cert-manager-robot.values.yaml",
    "content": "# Configuration for the cert-manager chart.\n# Reference: https://github.com/jetstack/cert-manager/blob/master/deploy/charts/cert-manager/values.yaml\n\nglobal:\n  # Reduce verbosity of text-logging.\n  logLevel: 1\n\n# Install CRDs by helm chart that webhook works in different namespace as cert-manager\ninstallCRDs: true\n\n# Disable leader-elect for cert-manager and cainjector.\n# Since we only have one replica running, we don't\n# need leader election to ensure only one instance is active.\n# By turning this off, the leader will not update its leases in etcd every N seconds which\n# ultimately reduces etcd disk writes.\n#\n# To ensure that we only have one replica running, we need to use the Recreate\n# deployment strategy.\nextraArgs:\n  - --leader-elect=false\nstrategy:\n  type: Recreate\n\ncainjector:\n  extraArgs:\n    - --leader-elect=false\n  strategy:\n    type: Recreate\n"
  },
  {
    "path": "src/app_charts/base/cloud/app-management-policy.yaml",
    "content": "# This policy lets app-rollout and chart-assigment controllers operate on the\n# apps & registry CRDs. It also grants chart-assignment-controller the\n# cluster-admin role, which lets it install any Helm chart. This is a very\n# broad role, but since many Helm charts install ClusterRoleBindings it's not\n# possible to do it without cluster-admin or something equivalent.\n\n# For app-rollout-controller\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:app-rollout-controller:base\n  labels:\n    app-rollout-controller.cloudrobotics.com/aggregate-to-app-rollout: \"true\"\nrules:\n- apiGroups:\n  - registry.cloudrobotics.com\n  resources:\n  - robots\n  - robots/status\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - apps\n  - approllouts\n  - approllouts/status\n  - chartassignments\n  - chartassignments/status\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - chartassignments\n  verbs:\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - approllouts/status\n  verbs:\n  - update\n  - patch\n---\n# Aggregated role for app-rollout-controller\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:app-rollout-controller\naggregationRule:\n  clusterRoleSelectors:\n  - matchLabels:\n      app-rollout-controller.cloudrobotics.com/aggregate-to-app-rollout: \"true\"\nrules: []  # The control plane automatically fills in the rules\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: app-rollout-controller\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cloud-robotics:app-rollout-controller\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cloud-robotics:app-rollout-controller\nsubjects:\n- namespace: {{ .Release.Namespace }}\n  kind: ServiceAccount\n  name: app-rollout-controller\n---\n# For chart-assignment-controller\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:chart-assignment-controller:base\n  labels:\n    chart-assignment-controller.cloudrobotics.com/aggregate-to-chart-assignment: \"true\"\nrules:\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - chartassignments\n  - chartassignments/status\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - chartassignments/status\n  verbs:\n  - update\n  - patch\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - resourcesets\n  - resourcesets/status\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:chart-assignment-controller:namespace-admin\n  labels:\n    chart-assignment-controller.cloudrobotics.com/aggregate-to-chart-assignment: \"true\"\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n---\n# Aggregated role for chart-assignment-controller\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:chart-assignment-controller\naggregationRule:\n  clusterRoleSelectors:\n  - matchLabels:\n      chart-assignment-controller.cloudrobotics.com/aggregate-to-chart-assignment: \"true\"\n  - matchLabels:\n      rbac.authorization.k8s.io/aggregate-to-admin: \"true\"\nrules: []  # The control plane automatically fills in the rules\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: chart-assignment-controller\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cloud-robotics:chart-assignment-controller\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cloud-robotics:chart-assignment-controller\nsubjects:\n- namespace: {{ .Release.Namespace }}\n  kind: ServiceAccount\n  name: chart-assignment-controller\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cloud-robotics:chart-assignment-controller:cluster-admin\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- namespace: {{ .Release.Namespace }}\n  kind: ServiceAccount\n  name: chart-assignment-controller\n"
  },
  {
    "path": "src/app_charts/base/cloud/app-management.yaml",
    "content": "{{ if eq .Values.app_management \"true\" }}\n# app-rollout-controller\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app-rollout-controller\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app-rollout-controller\n  template:\n    metadata:\n      labels:\n        app: app-rollout-controller\n    spec:\n      serviceAccountName: app-rollout-controller\n      containers:\n      - name: app-rollout-controller\n        image: {{ .Values.registry }}{{ .Values.images.app_rollout_controller }}\n        args:\n        - \"--params=\\\n          domain={{ .Values.domain }},\\\n          project={{ .Values.project }},\\\n          ingress_ip={{ .Values.ingress_ip }},\\\n          registry={{ .Values.registry }},\\\n          deploy_environment={{ .Values.deploy_environment }},\\\n          region={{ .Values.region }},\\\n          use_tv_verbose={{ .Values.use_tv_verbose }}\"\n        - --webhook-port=9876\n        - --cert-dir=/tls\n        env:\n        - name: GOOGLE_CLOUD_PROJECT\n          value: {{ .Values.project }}\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          httpGet:\n            port: 8080\n            path: /healthz\n        ports:\n        - name: webhook\n          containerPort: 9876\n        volumeMounts:\n        - mountPath: /tls\n          name: tls\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true  \n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n      volumes:\n      - name: tls\n        secret:\n          secretName: app-rollout-controller-tls\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: app-rollout-controller\nspec:\n  type: ClusterIP\n  ports:\n  - port: 443\n    targetPort: webhook\n  selector:\n    app: app-rollout-controller\n---\n# The app rollout controller runs admission webhooks, which need to be served via TLS.\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: app-rollout-controller\nspec:\n  secretName: app-rollout-controller-tls\n  commonName: app-rollout-controller.{{ .Release.Namespace }}.svc\n  dnsNames:\n    - app-rollout-controller.{{ .Release.Namespace }}.svc\n    - app-rollout-controller.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: ClusterIssuer\n    name: cluster-authority\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: app-rollout-controller\n  annotations:\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/app-rollout-controller\nwebhooks:\n- name: apps.apps.cloudrobotics.com\n  admissionReviewVersions: [\"v1\"]\n  failurePolicy: Fail\n  clientConfig:\n    service:\n      namespace: {{ .Release.Namespace }}\n      name: app-rollout-controller\n      path: /app/validate\n  rules:\n  - apiGroups:\n    - apps.cloudrobotics.com\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - apps\n  sideEffects: None\n- name: approllouts.apps.cloudrobotics.com\n  admissionReviewVersions: [\"v1\"]\n  failurePolicy: Fail\n  clientConfig:\n    service:\n      namespace: {{ .Release.Namespace }}\n      name: app-rollout-controller\n      path: /approllout/validate\n  rules:\n  - apiGroups:\n    - apps.cloudrobotics.com\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - approllouts\n  sideEffects: None\n---\n# chart-assignment-controller\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chart-assignment-controller\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: chart-assignment-controller\n  template:\n    metadata:\n      labels:\n        app: chart-assignment-controller\n    spec:\n      serviceAccountName: chart-assignment-controller\n      containers:\n      - name: chart-assignment-controller\n        image: {{ .Values.registry }}{{ .Values.images.chart_assignment_controller }}\n        args:\n        - \"--cloud-cluster=true\"\n        - \"--webhook-enabled=true\"\n        - \"--webhook-port=9876\"\n        - \"--cert-dir=/tls\"\n        env:\n        - name: GOOGLE_CLOUD_PROJECT\n          value: {{ .Values.project }}\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          httpGet:\n            port: 8080\n            path: /healthz\n        ports:\n        - name: webhook\n          containerPort: 9876\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true  \n        volumeMounts:\n        - name: tls\n          mountPath: /tls\n        - name: tmp\n          mountPath: /tmp\n      volumes:\n      - name: tls\n        secret:\n          secretName: chart-assignment-controller-tls\n      - name: tmp\n        emptyDir:\n          medium: Memory\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n---\n# The chart assignment controller runs admission webhooks, which need to be served via TLS.\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: chart-assignment-controller\nspec:\n  secretName: chart-assignment-controller-tls\n  commonName: chart-assignment-controller.{{ .Release.Namespace }}.svc\n  dnsNames:\n    - chart-assignment-controller.{{ .Release.Namespace }}.svc\n    - chart-assignment-controller.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: ClusterIssuer\n    name: cluster-authority\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: chart-assignment-controller\nspec:\n  type: ClusterIP\n  ports:\n  - port: 443\n    targetPort: webhook\n  selector:\n    app: chart-assignment-controller\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: chart-assignment-controller\n  annotations:\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/chart-assignment-controller\nwebhooks:\n- name: chartassignments.apps.cloudrobotics.com\n  admissionReviewVersions: [\"v1\"]\n  failurePolicy: Fail\n  clientConfig:\n    service:\n      namespace: {{ .Release.Namespace }}\n      name: chart-assignment-controller\n      path: /chartassignment/validate\n  rules:\n  - apiGroups:\n    - apps.cloudrobotics.com\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - chartassignments\n  sideEffects: None\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/apps-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: apps.apps.cloudrobotics.com\n  annotations:\n    helm.sh/resource-policy: keep\nspec:\n  group: apps.cloudrobotics.com\n  names:\n    kind: App\n    plural: apps\n    singular: app\n  scope: Cluster\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                repository:\n                  type: string\n                version:\n                  type: string\n                components:\n                  type: object\n                  properties:\n                    cloud:\n                      type: object\n                      properties:\n                        name:\n                          type: string\n                        inline:\n                          type: string\n                    robot:\n                      type: object\n                      properties:\n                        name:\n                          type: string\n                        inline:\n                          type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: approllouts.apps.cloudrobotics.com\n  annotations:\n    helm.sh/resource-policy: keep\nspec:\n  group: apps.cloudrobotics.com\n  names:\n    kind: AppRollout\n    plural: approllouts\n    singular: approllout\n  scope: Cluster\n  versions: \n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      additionalPrinterColumns:\n      - jsonPath: .status.assignments\n        name: Assignments\n        type: integer\n      - jsonPath: .status.readyAssignments\n        name: Ready\n        type: integer\n      - jsonPath: .status.failedAssignments\n        name: Failed\n        type: integer\n      - jsonPath: .metadata.creationTimestamp\n        name: Age\n        type: date\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                appName:\n                  type: string\n                cloud:\n                  type: object\n                  properties:\n                    values:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                robots:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      values:\n                        type: object\n                        x-kubernetes-preserve-unknown-fields: true\n                      version:\n                        type: string\n                      selector:\n                        type: object\n                        properties:\n                          any:\n                            type: boolean\n                          matchLabels:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                          matchExpressions:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  type: array\n                                  items:\n                                    type: string\n            status:\n              type: object\n              properties:\n                observedGeneration:\n                  type: integer\n                assignments:\n                  type: integer\n                readyAssignments:\n                  type: integer\n                settledAssignments:\n                  type: integer\n                failedAssignments:\n                  type: integer\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastUpdateTime:\n                        type: string\n                        format: date-time\n                      lastTransitionTime:\n                        type: string\n                        format: date-time\n                      status:\n                        type: string\n                      type:\n                        type: string\n                      message:\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chartassignments.apps.cloudrobotics.com\n  annotations:\n    cr-syncer.cloudrobotics.com/spec-source: cloud\n    cr-syncer.cloudrobotics.com/filter-by-robot-name: \"True\"\n    helm.sh/resource-policy: keep\nspec:\n  group: apps.cloudrobotics.com\n  names:\n    kind: ChartAssignment\n    plural: chartassignments\n    singular: chartassignment\n  scope: Cluster\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      additionalPrinterColumns:\n      - jsonPath: .status.phase\n        name: Phase\n        type: string\n      - jsonPath: .status.observedGeneration\n        name: Generation\n        type: integer\n      - jsonPath: .metadata.creationTimestamp\n        name: Age\n        type: date\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                clusterName:\n                  type: string\n                namespaceName:\n                  type: string\n                chart:\n                  type: object\n                  properties:\n                    repository:\n                      type: string\n                    name:\n                      type: string\n                    version:\n                      type: string\n                    inline:\n                      type: string\n                    values:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n            status:\n              type: object\n              properties:\n                observedGeneration:\n                  type: integer\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastUpdateTime:\n                        type: string\n                        format: date-time\n                      lastTransitionTime:\n                        type: string\n                        format: date-time\n                      status:\n                        type: string\n                      type:\n                        type: string\n                      message:\n                        type: string\n                phase:\n                  type: string\n"
  },
  {
    "path": "src/app_charts/base/cloud/cert-ingress.yaml",
    "content": "# Owns the 'tls' block to ensure a cert is generated and avoids repetition of \n# the 'tls' block in other ingresses. Cert is associated via 'host' field.\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: certificate-ingress\nspec:\n  ingressClassName: nginx\n  tls:\n  - hosts:\n    - {{ .Values.domain }}\n  rules:\n  - {}"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager-certificates.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: selfsigned-ca\nspec:\n  isCA: true\n  duration: 8760h  # 1year. This needs to be at least 3x 90days\n  commonName: {{ .Values.domain }}\n  secretName: cluster-authority\n  privateKey:\n    algorithm: ECDSA\n    size: 256\n  issuerRef:\n    name: selfsigned-issuer\n    kind: ClusterIssuer\n    group: cert-manager.io\n{{- if .Values.certificate_provider }}\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: cloud-robotics\nspec:\n  commonName: {{ .Values.domain }}\n  secretName: tls\n  dnsNames:\n    - {{ .Values.domain }}\n{{- if eq .Values.certificate_provider \"lets-encrypt\" }}\n  issuerRef:\n    name: letsencrypt-prod\n    kind: ClusterIssuer\n{{- else if eq .Values.certificate_provider \"google-cas\" }}\n  issuerRef:\n    name: google-cas\n    group: cas-issuer.jetstack.io\n    kind: GoogleCASClusterIssuer\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager-google-cas-issuer.yaml",
    "content": "{{- if eq .Values.certificate_provider \"google-cas\" }}\n# This includes all resources expanded from the cert-manager chart using\n# the values in ../cert-manager-cloud.values.yaml.\n# Some pseudo-variables that were inserted there are replaced with actual runtime values.\n{{ .Files.Get \"files/cert-manager-google-cas-issuer-chart.cloud.yaml\" | replace \"HELM-NAMESPACE\" .Release.Namespace | replace \"PROJECT-ID\" .Values.project }}\n{{- end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager-issuers.yaml",
    "content": "# A self-signing issuer for cluster-internal services.\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: selfsigned-issuer\nspec:\n  selfSigned: {}\n---\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: cluster-authority\nspec:\n  ca:\n    secretName: cluster-authority\n{{- if eq .Values.certificate_provider \"lets-encrypt\" }}\n---\n# While an Issuer may satisfy our current needs within the default namespace,\n# anticipating future growth and potential deployments in additional namespaces,\n# adopting a ClusterIssuer offers a more scalable and versatile solution.\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt-prod\nspec:\n  acme:\n    server: https://acme-v02.api.letsencrypt.org/directory\n    email: \"{{ .Values.owner_email }}\"\n    privateKeySecretRef:\n      name: letsencrypt-prod\n    # We can't use dns01 since we don't control the dns-zone that endpoints uses.\n    solvers:\n      - http01:\n          ingress:\n            class: nginx\n{{- else if eq .Values.certificate_provider \"google-cas\" }}\n---\n# Issuer for Google's Certificate Authority service (CAS) using the google-cas-issuer project.\n# https://github.com/jetstack/google-cas-issuer\napiVersion: cas-issuer.jetstack.io/v1beta1\nkind: GoogleCASClusterIssuer\nmetadata:\n  name: google-cas\nspec:\n  project: {{ .Values.project }}\n  location: {{ .Values.region }}\n  caPoolId: \"{{ .Values.project }}-ca-pool\"\n{{- end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/cert-manager.yaml",
    "content": "# This includes all resources expanded from the cert-manager chart using\n# the values in ../cert-manager-cloud.values.yaml.\n# Some pseudo-variables that were inserted there are replaced with actual runtime values.\n{{ .Files.Get \"files/cert-manager-chart.cloud.yaml\" | replace \"HELM-NAMESPACE\" .Release.Namespace | replace \"PROJECT-ID\" .Values.project }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/cr-syncer-auth-webhook.yaml",
    "content": "{{ if eq .Values.onprem_federation \"true\" }}\n# The cr-syncer-auth-webhook verifies that requests from the cr-syncer are\n# limited to the robot named in the credentials.\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cr-syncer-auth-webhook\nspec:\n  selector:\n    matchLabels:\n      app: cr-syncer-auth-webhook\n  template:\n    metadata:\n      labels:\n        app: cr-syncer-auth-webhook\n    spec:\n      containers:\n      - name: cr-syncer-auth-webhook\n        image: {{ .Values.registry }}{{ .Values.images.cr_syncer_auth_webhook }}\n        args:\n        - --port=8080\n        - --accept-legacy-service-account-credentials\n        - --token-vendor=http://token-vendor.app-token-vendor.svc.cluster.local\n        ports:\n        - name: webhook\n          containerPort: 8080\n        readinessProbe:\n          httpGet:\n            path: /healthz\n            port: 8080\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8080\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n      serviceAccountName: cr-syncer-auth-webhook\n---\n# The incoming request from the cr-syncer will be extended with a header to\n# impersonate this SA if it passes the webhook's policy checks.\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: cr-syncer-auth-webhook\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: cr-syncer-auth-webhook\n  labels:\n    app: cr-syncer-auth-webhook\nspec:\n  ports:\n  - port: 80\n    targetPort: 8080\n    protocol: TCP\n    name: http\n  selector:\n    app: cr-syncer-auth-webhook\n  type: ClusterIP\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/cr-syncer-policy.yaml",
    "content": "{{ if eq .Values.onprem_federation \"true\" }}\n# This policy lets the cr-syncer operate on the apps & registry CRDs.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:cr-syncer:base\n  labels:\n    cr-syncer.cloudrobotics.com/aggregate-to-robot-service: \"true\"\nrules:\n\n# To sync the specs from the cloud to the robot, the cr-syncer needs to read\n# the resources in the cloud cluster. Note that the Robot and ChartAssignment\n# CRDs enable the /status subresource, whereas the RobotType does not.\n- apiGroups:\n  - registry.cloudrobotics.com\n  resources:\n  - robots\n  - robots/status\n  - robottypes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - chartassignments\n  - chartassignments/status\n  verbs:\n  - get\n  - list\n  - watch\n\n# Only the /status subresource can be updated. It's important that the robot\n# can update the status but not the spec, or it could run code on other robots.\n- apiGroups:\n  - registry.cloudrobotics.com\n  resources:\n  - robots/status\n  verbs:\n  - update\n- apiGroups:\n  - apps.cloudrobotics.com\n  resources:\n  - chartassignments/status\n  verbs:\n  - update\n---\n# This aggregate role will combine all roles with the given label. This means\n# that policy can easily be added for CRDs beyond those listed above.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:cr-syncer\naggregationRule:\n  clusterRoleSelectors:\n  - matchLabels:\n      cr-syncer.cloudrobotics.com/aggregate-to-robot-service: \"true\"\nrules: []  # The control plane automatically fills in the rules\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cloud-robotics:cr-syncer\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cloud-robotics:cr-syncer\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: robot-service@{{ .Values.project }}.iam.gserviceaccount.com\n# The grant for the cr-syncer-auth-webhook replaces the grant for the\n# robot-service@ account.\n- namespace: {{ .Release.Namespace }}\n  kind: ServiceAccount\n  name: cr-syncer-auth-webhook\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/domain-redirect.yaml",
    "content": "{{ $endpointsURL := print \"www.endpoints.\" .Values.project \".cloud.goog\" }}\n{{ if ne $endpointsURL .Values.domain }}\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: domain-redirect\n  annotations:\n    nginx.ingress.kubernetes.io/permanent-redirect: http://{{ .Values.domain }}\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: \"www.endpoints.{{ .Values.project }}.cloud.goog\"\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            # This isn't used, but needs to be specified.\n            name: dummy\n            port:\n              number: 80\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/fluentd-metrics.yaml",
    "content": "# Adds a Prometheus ServiceMonitor for scraping the fluentd metrics.\n# By default, google-fluentd exports some Prometheus metrics on port 24231.\napiVersion: v1\nkind: Service\nmetadata:\n  name: fluentd-metrics\n  labels:\n    app: fluentd-metrics\n  namespace: kube-system\nspec:\n  ports:\n  - port: 24231\n    name: metrics\n  selector:\n    k8s-app: fluentd-gcp\n  type: ClusterIP\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: fluentd-metrics\n  labels:\n    prometheus: kube-prometheus\n  namespace: kube-system\nspec:\n  endpoints:\n  - port: metrics\n    path: /metrics\n    interval: 10s\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n  selector:\n    matchLabels:\n      app: fluentd-metrics\n"
  },
  {
    "path": "src/app_charts/base/cloud/kubernetes-api.yaml",
    "content": "{{ if eq .Values.onprem_federation \"true\" }}\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: kubernetes-api\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: http://cr-syncer-auth-webhook.default.svc.cluster.local/auth\n    nginx.ingress.kubernetes.io/auth-response-headers: Authorization\n    nginx.ingress.kubernetes.io/rewrite-target: /$2\n    nginx.ingress.kubernetes.io/backend-protocol: HTTPS\n    nginx.ingress.kubernetes.io/proxy-read-timeout: \"600\"  # seconds\n    nginx.ingress.kubernetes.io/client-body-buffer-size: \"50m\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /apis/core.kubernetes($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: kubernetes\n            port:\n              number: 443\n{{ end }}"
  },
  {
    "path": "src/app_charts/base/cloud/namespace.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{ .Release.Namespace }}\n  labels:\n    certmanager.k8s.io/disable-validation: \"true\"\n"
  },
  {
    "path": "src/app_charts/base/cloud/nginx-ingress-controller-policy.yaml",
    "content": "# Source: ingress-nginx/templates/controller-serviceaccount.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: ingress-nginx\nautomountServiceAccountToken: true\n---\n# Source: ingress-nginx/templates/clusterrole.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: ingress-nginx\nrules:\n  - apiGroups:\n      - ''\n    resources:\n      - configmaps\n      - endpoints\n      - nodes\n      - pods\n      - secrets\n    verbs:\n      - list\n      - watch\n  - apiGroups:\n      - ''\n    resources:\n      - nodes\n    verbs:\n      - get\n  - apiGroups:\n      - ''\n    resources:\n      - services\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - networking.k8s.io\n    resources:\n      - ingresses\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - networking.k8s.io\n    resources:\n      - ingresses/status\n    verbs:\n      - update\n  - apiGroups:\n      - networking.k8s.io\n    resources:\n      - ingressclasses\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resourceNames:\n      - ingress-controller-leader\n    resources:\n      - leases\n    verbs:\n      - get\n      - update\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n  - apiGroups:\n      - ''\n    resources:\n      - events\n    verbs:\n      - create\n      - patch\n  - apiGroups:\n      - discovery.k8s.io\n    resources:\n      - endpointslices\n    verbs:\n      - list\n      - watch\n      - get\n---\n# Source: ingress-nginx/templates/clusterrolebinding.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: ingress-nginx\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: ingress-nginx\nsubjects:\n  - kind: ServiceAccount\n    name: ingress-nginx\n    namespace: {{ .Release.Namespace }}\n---\n# Source: ingress-nginx/templates/controller-role.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: ingress-nginx\nrules:\n  - apiGroups:\n      - ''\n    resources:\n      - namespaces\n    verbs:\n      - get\n  - apiGroups:\n      - ''\n    resources:\n      - configmaps\n      - pods\n      - secrets\n      - endpoints\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - list\n      - watch\n  - apiGroups:\n      - ''\n    resources:\n      - services\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - networking.k8s.io\n    resources:\n      - ingresses\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - networking.k8s.io\n    resources:\n      - ingresses/status\n    verbs:\n      - update\n  - apiGroups:\n      - networking.k8s.io\n    resources:\n      - ingressclasses\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - discovery.k8s.io\n    resources:\n      - endpointslices\n    verbs:\n      - list\n      - watch\n      - get\n  - apiGroups:\n      - ''\n    resources:\n      - configmaps\n    resourceNames:\n      - ingress-controller-leader\n    verbs:\n      - get\n      - update\n  - apiGroups:\n      - ''\n    resources:\n      - configmaps\n    verbs:\n      - create\n  - apiGroups:\n      - ''\n    resources:\n      - events\n    verbs:\n      - create\n      - patch\n---\n# Source: ingress-nginx/templates/controller-rolebinding.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: ingress-nginx\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: ingress-nginx\nsubjects:\n  - kind: ServiceAccount\n    name: ingress-nginx\n    namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/nginx-ingress-controller.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx-ingress-controller\ndata:\n  # This is the same as the default but with the addition of $http_x_forwarded_for,\n  # which is useful when the GKE Global Application LB is also pointed at nginx.\n  # https://cloud.google.com/load-balancing/docs/https#x-forwarded-for_header\n  log-format-upstream: $remote_addr - $remote_user - $http_x_forwarded_for [$time_local]\n    \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" $request_length\n    $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr\n    $upstream_response_length $upstream_response_time $upstream_status $req_id\n  # The token-vendor checks the Original-URI header to accept tokens from query\n  # parameters.\n  proxy-add-original-uri-header: \"true\"\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-ingress-controller\n  labels:\n    k8s-app: nginx-ingress-controller\nspec:\n  selector:\n    matchLabels:\n      k8s-app: nginx-ingress-controller\n  template:\n    metadata:\n      labels:\n        k8s-app: nginx-ingress-controller\n    spec:\n      serviceAccountName: ingress-nginx\n      dnsPolicy: ClusterFirst\n      containers:\n        - name: nginx-ingress-controller\n          image: registry.k8s.io/ingress-nginx/controller-chroot:v1.8.4@sha256:76100ab4c1b3cdc2697dd26492ba42c6519e99c5df1bc839ac5d6444a2c58d17\n          lifecycle:\n            preStop:\n              exec:\n                command:\n                  - /wait-shutdown\n          resources:\n            requests:\n              memory: \"1Gi\"\n              cpu: 1\n          args:\n            - /nginx-ingress-controller\n            - --v=1\n            - --default-backend-service=kube-system/default-http-backend\n            - --publish-service=$(POD_NAMESPACE)/nginx-ingress-lb\n            - --election-id=ingress-controller-leader\n            - --ingress-class=nginx\n            - --configmap=$(POD_NAMESPACE)/nginx-ingress-controller\n            - --default-ssl-certificate=default/tls\n          securityContext:\n            capabilities:\n              drop:\n                - ALL\n              add:\n                - NET_BIND_SERVICE\n                - SYS_CHROOT\n            runAsUser: 101\n            allowPrivilegeEscalation: true\n          env:\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: POD_NAMESPACE\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.namespace\n            - name: LD_PRELOAD\n              value: /usr/local/lib/libmimalloc.so\n          livenessProbe:\n            httpGet:\n              path: /healthz\n              port: 10254\n              scheme: HTTP\n            initialDelaySeconds: 10\n            periodSeconds: 10\n            timeoutSeconds: 1\n            successThreshold: 1\n            failureThreshold: 5\n          readinessProbe:\n            httpGet:\n              path: /healthz\n              port: 10254\n              scheme: HTTP\n            initialDelaySeconds: 10\n            periodSeconds: 10\n            timeoutSeconds: 1\n            successThreshold: 1\n            failureThreshold: 3\n          ports:\n            - name: http\n              containerPort: 80\n            - name: https\n              containerPort: 443\n            - name: healthz\n              containerPort: 10254\n      nodeSelector:\n        kubernetes.io/os: linux\n      terminationGracePeriodSeconds: 300\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-ingress-lb\n  labels:\n    app: nginx-ingress-lb\nspec:\n  type: LoadBalancer\n  externalTrafficPolicy: Local\n  loadBalancerIP: {{ .Values.ingress_ip }}\n  ports:\n    - port: 80\n      name: http\n      targetPort: 80\n      appProtocol: HTTP\n    - port: 443\n      name: https\n      targetPort: 443\n      appProtocol: HTTPS\n  selector:\n    k8s-app: nginx-ingress-controller\n---\napiVersion: networking.k8s.io/v1\nkind: IngressClass\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n  name: nginx\n  annotations:\n    ingressclass.kubernetes.io/is-default-class: \"true\"\nspec:\n  controller: k8s.io/ingress-nginx\n---\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: nginx-ingress-controller\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: nginx-ingress-controller\n  minReplicas: 1\n  maxReplicas: 5\n  metrics:\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: 80\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-ingress-controller-metrics\n  labels:\n    app: nginx-ingress-controller-metrics\nspec:\n  ports:\n    - port: 10254\n      name: healthz\n  selector:\n    k8s-app: nginx-ingress-controller\n  type: ClusterIP\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: nginx-ingress-controller-metrics\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n    - port: healthz\n      relabelings:\n        - sourceLabels: [__meta_kubernetes_pod_node_name]\n          targetLabel: instance\n  selector:\n    matchLabels:\n      app: nginx-ingress-controller-metrics\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx-dashboards-json\n  labels:\n    grafana: \"1\"\ndata:\n  nginx.json: |-\n{{ .Files.Get \"files/nginx.json\" | indent 4 }}\n  request-handling-performance.json: |-\n{{ .Files.Get \"files/request-handling-performance.json\" | indent 4 }}\n\n"
  },
  {
    "path": "src/app_charts/base/cloud/oauth2-proxy.yaml",
    "content": "{{ if and (ne .Values.oauth2_proxy.client_id \"\") (ne .Values.oauth2_proxy.client_secret \"\") }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: oauth2-proxy\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: oauth2-proxy\n  template:\n    metadata:\n      labels:\n        app: oauth2-proxy\n    spec:\n      containers:\n      - name: oauth2-proxy\n        args:\n        - --provider=oidc\n        - --oidc-issuer-url=https://accounts.google.com\n        - --email-domain=*\n        - --upstream=http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/\n        - --upstream=https://{{ .Values.domain }}/\n        - --http-address=0.0.0.0:8080\n        - --pass-access-token\n        - --pass-host-header\n        - \"--scope=profile email https://www.googleapis.com/auth/iam\"\n        - --cookie-expire=168h\n        - --cookie-refresh=1h\n        env:\n        - name: OAUTH2_PROXY_CLIENT_ID\n          value: {{ .Values.oauth2_proxy.client_id }}\n        - name: OAUTH2_PROXY_CLIENT_SECRET\n          value: {{ .Values.oauth2_proxy.client_secret }}\n        - name: OAUTH2_PROXY_COOKIE_SECRET\n          value: {{ .Values.oauth2_proxy.cookie_secret }}\n        image: quay.io/oauth2-proxy/oauth2-proxy:v7.5.1\n        ports:\n        - name: http\n          containerPort: 8080\n          protocol: TCP\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: oauth2-proxy\nspec:\n  ports:\n  - port: 80\n    targetPort: 8080\n    protocol: TCP\n    name: http\n  selector:\n    app: oauth2-proxy\n  type: ClusterIP\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: oauth2-proxy\n  annotations:\n    nginx.ingress.kubernetes.io/rewrite-target: /apis/$2\n    nginx.ingress.kubernetes.io/proxy-read-timeout: \"600\"  # seconds\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /web-apis($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: oauth2-proxy\n            port:\n              name: http\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: oauth2-proxy-interactive\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: \"/oauth2\"\n        pathType: Prefix\n        backend:\n          service:\n            name: oauth2-proxy\n            port:\n              name: http\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/registry-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: robottypes.registry.cloudrobotics.com\n  annotations:\n    cr-syncer.cloudrobotics.com/spec-source: cloud\n    helm.sh/resource-policy: keep\nspec:\n  group: registry.cloudrobotics.com\n  names:\n    kind: RobotType\n    plural: robottypes\n    singular: robottype\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              required: ['make', 'model']\n              maxProperties: 2\n              properties:\n                make:\n                  type: string\n                model:\n                  type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: robots.registry.cloudrobotics.com\n  annotations:\n    cr-syncer.cloudrobotics.com/filter-by-robot-name: \"True\"\n    cr-syncer.cloudrobotics.com/status-subtree: \"robot\"\n    cr-syncer.cloudrobotics.com/spec-source: cloud\n    helm.sh/resource-policy: keep\nspec:\n  group: registry.cloudrobotics.com\n  names:\n    kind: Robot\n    plural: robots\n    singular: robot\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              maxProperties: 3\n              properties:\n                type:\n                  type: string\n                project:\n                  type: string\n            status:\n              type: object\n              properties:\n                cloud:\n                  type: object\n                  x-kubernetes-preserve-unknown-fields: true\n                robot:\n                  type: object\n                  properties:\n                    info:\n                      type: object\n                      additionalProperties:\n                        type: string\n                    updateTime:\n                      type: string\n                    state:\n                      type: string\n                      enum:\n                        - UNDEFINED\n                        - UNAVAILABLE\n                        - AVAILABLE\n                        - EMERGENCY_STOP\n                        - ERROR\n                    lastStateChangeTime:\n                      type: string\n                    batteryPercentage:\n                      type: number\n                    emergencyStopButtonPressed:\n                      type: boolean\n                configuration:\n                  type: object\n                  properties:\n                    trolleyAttached:\n                      type: boolean\n"
  },
  {
    "path": "src/app_charts/base/cloud/registry-policy.yaml",
    "content": "# This policy lets the human-acl GCP SA register robots. For context, see the\n# IAM policy in service-account.tf.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cloud-robotics:robot-creator\nrules:\n- apiGroups:\n  - registry.cloudrobotics.com\n  resources:\n  - robots\n  - robots/status\n  verbs:\n  - create\n  - get\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cloud-robotics:human-acl\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cloud-robotics:robot-creator\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: human-acl@{{ .Values.project }}.iam.gserviceaccount.com\n"
  },
  {
    "path": "src/app_charts/base/cloud/relay-dashboards.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: relay-dashboards-json\n  labels:\n    grafana: \"1\"\ndata:\n  relay-dashboard.json: |-\n{{ .Files.Get \"files/relay-dashboard.json\" | indent 4 }}\n"
  },
  {
    "path": "src/app_charts/base/cloud/token-vendor-app-fwd.yaml",
    "content": "# The Token Vendor was moved to the app namespace.\n# We create this service here in default namespace \n# as some codepaths still use the hard-coded\n# \"token-vendor.default.svc.cluster.local\" address.\napiVersion: v1\nkind: Service\nmetadata:\n  name: token-vendor\n  annotations:\nspec:\n  ports:\n  - port: 80\n    targetPort: 9090\n    protocol: TCP\n    name: token-vendor\n  type: ExternalName\n  externalName: token-vendor.app-token-vendor.svc.cluster.local"
  },
  {
    "path": "src/app_charts/base/cloud/token-vendor-rollout.yaml",
    "content": "apiVersion: apps.cloudrobotics.com/v1alpha1\nkind: AppRollout\nmetadata:\n  name: token-vendor\n  labels:\n    app: token-vendor\nspec:\n  appName: token-vendor-dev\n  cloud: {}\n"
  },
  {
    "path": "src/app_charts/base/fluent-bit-helm.sh",
    "content": "#!/bin/bash\n# needs at least helm v3.5.0\nOUTPUT=./robot/fluent-bit.yaml\nTEMPLATE_VERSION=0.48.9\nhelm repo add fluent https://fluent.github.io/helm-charts\nhelm repo update fluent\nhelm template fluent-bit fluent/fluent-bit --version ${TEMPLATE_VERSION} -f fluent-bit-values.yaml --skip-tests > ${OUTPUT}\n\nsed -i '1i\\{{ if and (eq .Values.robot_authentication \"true\") (eq .Values.fluentbit \"true\") }}' ${OUTPUT}\nsed -i '1i\\# !!! DO NOT EDIT THIS FILE !!!\\n# This file is autogenerated using src/app_charts/base/fluent-bit-helm.sh.\\n# See src/app_charts/base/README.md for update instructions.' ${OUTPUT}\nsed -i '$a\\{{ end }}' ${OUTPUT}\nsed -i 's/MY_ROBOT/{{ .Values.robot.name }}/' ${OUTPUT}\n\n# Add a template expressions for prepending a subdomain to the fluentbit Tag_Prefix\n# If no subdomain is supplied, the Tag_Prefix will resolve to \"kube.var.log.containers.\"\n# Otherwise, if subdomain is supplied (without the \".\" at the end), the Tag_Prefix will be \"<subdomain>.kube.var.log.containers.\"\nsed -i 's/kube\\.\\*/{{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*/' ${OUTPUT}\nsed -i 's/Tag_Prefix kube.var.log.containers./Tag_Prefix {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.var.log.containers./' ${OUTPUT}\n\n# This needs to be an actual Cloud zone so that it can be mapped\n# to a Monarch/Stackdriver region. TODO(swolter): We should make\n# this zone configurable to avoid confusing users.\nsed -i 's/MY_CLUSTER_LOCATION/europe-west1-c/' ${OUTPUT}\n"
  },
  {
    "path": "src/app_charts/base/fluent-bit-values.yaml",
    "content": "image:\n  pullPolicy: IfNotPresent\n\nenv:\n  - name: MY_NODE_NAME\n    valueFrom:\n      fieldRef:\n        fieldPath: spec.nodeName\n\n## https://docs.fluentbit.io/manual/administration/configuring-fluent-bit/configuration-file\nconfig:\n  service: |\n    [SERVICE]\n        Daemon Off\n        Flush {{ .Values.flush }}\n        Log_Level {{ .Values.logLevel }}\n        Parsers_File custom_parsers.conf\n        HTTP_Server On\n        HTTP_Listen 0.0.0.0\n        HTTP_Port {{ .Values.metricsPort }}\n        Health_Check On\n\n  ## https://docs.fluentbit.io/manual/pipeline/parsers\n  customParsers: |\n    # Merges multi-line Abseil logs. The regexs assume that any line which does not start\n    # with an Abseil log preamble is part of the previous log message. No assumptions on\n    # indentation or similar are made.\n    [MULTILINE_PARSER]\n        Name          absl_logs_multiline\n        Type          regex\n        Flush_timeout 1000\n        #\n        # Regex rules for multiline parsing\n        # ---------------------------------\n        #\n        # configuration hints:\n        #\n        #  - first state always has the name: start_state\n        #  - every field in the rule must be inside double quotes\n        #\n        # rules |   state name  | regex pattern                                                         | next state\n        # ------|---------------|---------------------------------------------------------------------------------------------\n        Rule      \"start_state\"   \"/^((W|I|E|F))([0-9]{4}) ([^ ]+)\\s+([-0-9]+) (\\S+:\\d+)] (.*)$/\"           \"cont\"\n        Rule      \"cont\"          \"/^(?!(((W|I|E|F))([0-9]{4}) ([^ ]+)\\s+([-0-9]+) (\\S+:\\d+)]))(.*)$/\"      \"cont\"\n\n    # A parser for Abseil log files: https://abseil.io/docs/cpp/guides/logging#prefix\n    [PARSER]\n        Name        absl_logs\n        Format      regex\n        Regex       ^(?<severity>(W|I|E|F))([0-9]{4}) (?<time>[^ ]+)\\s+(?<pid>[-0-9]+) (?<source>\\S+:\\d+)] (?<message>[\\s\\S]*)$\n        Time_Key    time\n        Time_Format %H:%M:%S.%L\n        Time_Keep   On\n        Types       pid:integer\n\n  ## https://docs.fluentbit.io/manual/pipeline/inputs\n  inputs: |\n    [INPUT]\n        Name tail\n        Path /var/log/containers/*.log\n        # Adding the 'absl_logs_multiline' does not work as intended.\n        # It must be specified as a dedicated filter (see below).\n        Multiline.Parser docker, cri, go, python\n        Tag kube.*\n        Mem_Buf_Limit 5MB\n        Skip_Long_Lines On\n\n    [INPUT]\n        Name systemd\n        Tag k8s_node.${MY_NODE_NAME}\n        Systemd_Filter  _SYSTEMD_UNIT=containerd.service\n        Systemd_Filter  _SYSTEMD_UNIT=etcd.service\n        Systemd_Filter  _SYSTEMD_UNIT=kube-apiserver.service\n        Systemd_Filter  _SYSTEMD_UNIT=kube-controller-manager.service\n        Systemd_Filter  _SYSTEMD_UNIT=kube-scheduler.service\n        Systemd_Filter  _SYSTEMD_UNIT=kubelet.service\n        Systemd_Filter  _SYSTEMD_UNIT=sshd.service\n        Read_From_Tail On\n\n    [INPUT]\n        Name systemd\n        Tag kernel.${MY_NODE_NAME}\n        Systemd_Filter _TRANSPORT=kernel\n        Read_From_Tail On\n\n  ## https://docs.fluentbit.io/manual/pipeline/filters\n  filters: |\n    [FILTER]\n        Name kubernetes\n        Match kube.*\n        Merge_Log On\n        Keep_Log Off\n        K8S-Logging.Parser On\n        K8S-Logging.Exclude On\n\n    # Joins multiple Abseil logs into a single line.\n    [FILTER]\n        Name                    multiline\n        Match                   kube.*\n        Multiline.Key_content   log\n        Multiline.Parser        absl_logs_multiline\n\n    # Applies the absl_logs parser to the 'log' field.\n    [FILTER]\n        Name          parser\n        Match         kube.*\n        Key_Name      log\n        Parser        absl_logs\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_equals level info\n        Add severity INFO\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_equals level warning\n        Add severity WARNING\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_equals level warn\n        Add severity WARNING\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_equals level error\n        Add severity ERROR\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_matches log ^.*\\[\\sinfo\\].*$\n        Add severity INFO\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_matches log ^.*\\[\\swarn\\].*$\n        Add severity WARNING\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_matches log .*\\[error\\].*\n        Add severity ERROR\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_matches log .*(type=\"Info\"|level=info).*\n        Add severity INFO\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_matches log .*(type=\"Warning\"|level=warning).*\n        Add severity WARNING\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_matches log .*(type=\"Error\"|level=error).*\n        Add severity ERROR\n\n    # We're setting the logName here and below to avoid a high cardinality field on stackdriver\n    # See https://github.com/fluent/fluent-bit/issues/9897\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_equals stream stderr\n        Add severity ERROR\n        Set logging.googleapis.com/logName stderr\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Condition Key_value_equals stream stdout\n        Add severity INFO\n        Set logging.googleapis.com/logName stdout\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Remove _p\n\n    [FILTER]\n        Name          modify\n        Match         kube.*\n        Rename log message\n\n  ## https://docs.fluentbit.io/manual/pipeline/outputs\n  outputs: |\n    [OUTPUT]\n        Name stackdriver\n        Match kube.*\n        Tag_Prefix kube.var.log.containers.\n        Resource k8s_container\n        k8s_cluster_name MY_ROBOT\n        k8s_cluster_location MY_CLUSTER_LOCATION\n        severity_key severity\n        Workers 1\n\n    [OUTPUT]\n        Name stackdriver\n        Match k8s_node.*\n        Resource k8s_node\n        custom_k8s_regex ^(?<node_name>.*)$\n        k8s_cluster_name MY_ROBOT\n        k8s_cluster_location MY_CLUSTER_LOCATION\n        Workers 1\n\n    [OUTPUT]\n        Name stackdriver\n        Match kernel.*\n        Resource k8s_node\n        tag_prefix kernel.\n        custom_k8s_regex ^(?<node_name>.*)$\n        k8s_cluster_name MY_ROBOT\n        k8s_cluster_location MY_CLUSTER_LOCATION\n        Workers 1\n\nvolumeMounts:\n  - name: config\n    mountPath: /fluent-bit/etc/fluent-bit.conf\n    subPath: fluent-bit.conf\n  - name: config\n    mountPath: /fluent-bit/etc/custom_parsers.conf\n    subPath: custom_parsers.conf\n\ndaemonSetVolumes:\n  - name: varlog\n    hostPath:\n      path: /var/log\n  - name: runlog\n    hostPath:\n      path: /run/log\n  - name: etcmachineid\n    hostPath:\n      path: /etc/machine-id\n      type: File\n\ndaemonSetVolumeMounts:\n  - name: varlog\n    mountPath: /var/log\n  - name: runlog\n    mountPath: /run/log\n  - name: etcmachineid\n    mountPath: /etc/machine-id\n    readOnly: true\n\ntolerations:\n  - key: \"rtpc\"\n    operator: \"Exists\"\n    effect: \"NoSchedule\"\n"
  },
  {
    "path": "src/app_charts/base/relay-dashboard.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\",\n          \"uid\": \"grafana\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (method) (rate(broker_requests[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"method=\\\"{{method}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Broker Requests by method\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 0\n      },\n      \"id\": 11,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (method) (rate(broker_responses{result=\\\"ok\\\"}[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"method=\\\"{{method}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"# of successful Broker Responses by method\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 0\n      },\n      \"id\": 8,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (method,result) (rate(broker_responses{result!~\\\"(ok|timeout)\\\"}[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"method=\\\"{{method}}\\\",result=\\\"{{result}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"# of bad Broker Responses by method\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"id\": 4,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (job) (rate(broker_requests[$__rate_interval])) \",\n          \"interval\": \"\",\n          \"legendFormat\": \"job=\\\"{{job}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Broker Requests by relay \",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 7\n      },\n      \"id\": 14,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (job) (rate(broker_responses{result=\\\"ok\\\"}[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"job=\\\"{{job}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"# of successful Broker Responses by relay\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 7\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (job,result) (rate(broker_responses{result!~\\\"(ok|timeout)\\\"}[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"job=\\\"{{job}}\\\",result=\\\"{{result}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"# of bad Broker Responses by relay\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 14\n      },\n      \"id\": 12,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (backend) (rate(broker_requests[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"backend=\\\"{{backend}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Broker Requests by client\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 14\n      },\n      \"id\": 15,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (backend) (rate(broker_responses{result=\\\"ok\\\"}[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"backend=\\\"{{backend}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"# of successful Broker Responses by client\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"never\",\n            \"spanNulls\": true,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"short\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 14\n      },\n      \"id\": 16,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.0.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"exemplar\": true,\n          \"expr\": \"sum by (backend,result) (rate(broker_responses{result!~\\\"(ok|timeout)\\\"}[$__rate_interval]))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"backend=\\\"{{backend}}\\\",result=\\\"{{result}}\\\"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"# of bad Broker Responses by client\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"cards\": {},\n      \"color\": {\n        \"cardColor\": \"#b4ff00\",\n        \"colorScale\": \"sqrt\",\n        \"colorScheme\": \"interpolateOranges\",\n        \"exponent\": 0.5,\n        \"mode\": \"spectrum\"\n      },\n      \"dataFormat\": \"tsbuckets\",\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"Request duration buckets (in seconds) over time.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            }\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 12,\n        \"w\": 16,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"heatmap\": {},\n      \"hideZeroBuckets\": false,\n      \"highlightCards\": true,\n      \"id\": 6,\n      \"legend\": {\n        \"show\": false\n      },\n      \"maxDataPoints\": 25,\n      \"options\": {\n        \"calculate\": false,\n        \"calculation\": {\n          \"yBuckets\": {\n            \"scale\": {\n              \"type\": \"linear\"\n            }\n          }\n        },\n        \"cellGap\": 2,\n        \"cellValues\": {},\n        \"color\": {\n          \"exponent\": 0.5,\n          \"fill\": \"#b4ff00\",\n          \"mode\": \"scheme\",\n          \"reverse\": false,\n          \"scale\": \"exponential\",\n          \"scheme\": \"Oranges\",\n          \"steps\": 128\n        },\n        \"exemplars\": {\n          \"color\": \"rgba(255,0,255,0.7)\"\n        },\n        \"filterValues\": {\n          \"le\": 1e-9\n        },\n        \"legend\": {\n          \"show\": false\n        },\n        \"rowsFrame\": {\n          \"layout\": \"auto\"\n        },\n        \"showValue\": \"never\",\n        \"tooltip\": {\n          \"show\": true,\n          \"yHistogram\": false\n        },\n        \"yAxis\": {\n          \"axisPlacement\": \"left\",\n          \"reverse\": false,\n          \"unit\": \"s\"\n        }\n      },\n      \"pluginVersion\": \"9.1.7\",\n      \"reverseYBuckets\": false,\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(rate(broker_responses_durations_bucket[$__interval])) by (le)\",\n          \"format\": \"heatmap\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Request duration (in seconds) histogram\",\n      \"tooltip\": {\n        \"show\": true,\n        \"showHistogram\": false\n      },\n      \"type\": \"heatmap\",\n      \"xAxis\": {\n        \"show\": true\n      },\n      \"yAxis\": {\n        \"format\": \"short\",\n        \"logBase\": 1,\n        \"show\": true\n      },\n      \"yBucketBound\": \"auto\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 12,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 21\n      },\n      \"id\": 10,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"count(sum by(backend) (rate(broker_requests{method=\\\"server_request\\\", job=\\\"kubernetes-relay-server\\\"}[$__rate_interval]) > 0))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"number of backends\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of backends online\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"cards\": {},\n      \"color\": {\n        \"cardColor\": \"#b4ff00\",\n        \"colorScale\": \"sqrt\",\n        \"colorScheme\": \"interpolateOranges\",\n        \"exponent\": 0.5,\n        \"mode\": \"spectrum\"\n      },\n      \"dataFormat\": \"tsbuckets\",\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"Backend request duration buckets (in seconds) over time.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            }\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 12,\n        \"w\": 16,\n        \"x\": 0,\n        \"y\": 33\n      },\n      \"heatmap\": {},\n      \"hideZeroBuckets\": false,\n      \"highlightCards\": true,\n      \"id\": 17,\n      \"legend\": {\n        \"show\": false\n      },\n      \"maxDataPoints\": 25,\n      \"options\": {\n        \"calculate\": false,\n        \"calculation\": {},\n        \"cellGap\": 2,\n        \"cellValues\": {},\n        \"color\": {\n          \"exponent\": 0.5,\n          \"fill\": \"#b4ff00\",\n          \"mode\": \"scheme\",\n          \"reverse\": false,\n          \"scale\": \"exponential\",\n          \"scheme\": \"Oranges\",\n          \"steps\": 128\n        },\n        \"exemplars\": {\n          \"color\": \"rgba(255,0,255,0.7)\"\n        },\n        \"filterValues\": {\n          \"le\": 1e-9\n        },\n        \"legend\": {\n          \"show\": false\n        },\n        \"rowsFrame\": {\n          \"layout\": \"auto\"\n        },\n        \"showValue\": \"never\",\n        \"tooltip\": {\n          \"show\": true,\n          \"yHistogram\": false\n        },\n        \"yAxis\": {\n          \"axisPlacement\": \"left\",\n          \"reverse\": false,\n          \"unit\": \"s\"\n        }\n      },\n      \"pluginVersion\": \"9.1.7\",\n      \"reverseYBuckets\": false,\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(rate(broker_backend_responses_durations_bucket[$__interval])) by (le)\",\n          \"format\": \"heatmap\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Backend request duration (in seconds) histogram\",\n      \"tooltip\": {\n        \"show\": true,\n        \"showHistogram\": false\n      },\n      \"type\": \"heatmap\",\n      \"xAxis\": {\n        \"show\": true\n      },\n      \"yAxis\": {\n        \"format\": \"short\",\n        \"logBase\": 1,\n        \"show\": true\n      },\n      \"yBucketBound\": \"auto\"\n    },\n    {\n      \"cards\": {},\n      \"color\": {\n        \"cardColor\": \"#b4ff00\",\n        \"colorScale\": \"sqrt\",\n        \"colorScheme\": \"interpolateOranges\",\n        \"exponent\": 0.5,\n        \"mode\": \"spectrum\"\n      },\n      \"dataFormat\": \"tsbuckets\",\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"Relay overhead duration buckets (in seconds) over time.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"custom\": {\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            }\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 12,\n        \"w\": 16,\n        \"x\": 0,\n        \"y\": 45\n      },\n      \"heatmap\": {},\n      \"hideZeroBuckets\": false,\n      \"highlightCards\": true,\n      \"id\": 18,\n      \"legend\": {\n        \"show\": false\n      },\n      \"maxDataPoints\": 25,\n      \"options\": {\n        \"calculate\": false,\n        \"calculation\": {},\n        \"cellGap\": 2,\n        \"cellValues\": {},\n        \"color\": {\n          \"exponent\": 0.5,\n          \"fill\": \"#b4ff00\",\n          \"mode\": \"scheme\",\n          \"reverse\": false,\n          \"scale\": \"exponential\",\n          \"scheme\": \"Oranges\",\n          \"steps\": 128\n        },\n        \"exemplars\": {\n          \"color\": \"rgba(255,0,255,0.7)\"\n        },\n        \"filterValues\": {\n          \"le\": 1e-9\n        },\n        \"legend\": {\n          \"show\": false\n        },\n        \"rowsFrame\": {\n          \"layout\": \"auto\"\n        },\n        \"showValue\": \"never\",\n        \"tooltip\": {\n          \"show\": true,\n          \"yHistogram\": false\n        },\n        \"yAxis\": {\n          \"axisPlacement\": \"left\",\n          \"reverse\": false,\n          \"unit\": \"s\"\n        }\n      },\n      \"pluginVersion\": \"9.1.7\",\n      \"reverseYBuckets\": false,\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"sum(rate(broker_overhead_durations_bucket[$__interval])) by (le)\",\n          \"format\": \"heatmap\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{le}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Relay overhead duration (in seconds) histogram\",\n      \"tooltip\": {\n        \"show\": true,\n        \"showHistogram\": false\n      },\n      \"type\": \"heatmap\",\n      \"xAxis\": {\n        \"show\": true\n      },\n      \"yAxis\": {\n        \"format\": \"short\",\n        \"logBase\": 1,\n        \"show\": true\n      },\n      \"yBucketBound\": \"auto\"\n    }\n  ],\n  \"schemaVersion\": 37,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"cloud-robotics\"\n  ],\n  \"templating\": {\n    \"list\": []\n  },\n  \"time\": {\n    \"from\": \"now-30m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Http Relay\",\n  \"uid\": \"p_rQjdS7k\",\n  \"version\": 2,\n  \"weekStart\": \"\"\n}"
  },
  {
    "path": "src/app_charts/base/robot/app-management.yaml",
    "content": "{{ if eq .Values.app_management \"true\" }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chart-assignment-controller\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: chart-assignment-controller\n  template:\n    metadata:\n      labels:\n        app: chart-assignment-controller\n    spec:\n      containers:\n      - name: chart-assignment-controller\n        image: {{ .Values.registry }}{{ .Values.images.chart_assignment_controller }}\n        args:\n        - --cloud-cluster=false\n        - --webhook-enabled={{ .Values.webhook.enabled }}\n        - --webhook-port=9876\n        - --cert-dir=/tls\n        - --trace-stackdriver-project-id={{ .Values.project }}\n        env:\n        - name: GOOGLE_CLOUD_PROJECT\n          value: {{ .Values.project }}\n        - name: ROBOT_NAME\n          value: \"{{ .Values.robot.name }}\"\n        livenessProbe:\n          initialDelaySeconds: 15\n          periodSeconds: 10\n          httpGet:\n            port: 8080\n            path: /healthz\n        ports:\n        - name: webhook\n          containerPort: 9876\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true  \n        volumeMounts:\n        - name: tls\n          mountPath: /tls\n        - name: tmp\n          mountPath: /tmp\n      volumes:\n      - name: tls\n        secret:\n          secretName: chart-assignment-controller-tls\n      - name: tmp\n        emptyDir:\n          medium: Memory\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n---\n# The chart assignment controller runs admission webhooks, which need to be served via TLS.\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: chart-assignment-controller\nspec:\n  secretName: chart-assignment-controller-tls\n  commonName: chart-assignment-controller.{{ .Release.Namespace }}.svc\n  dnsNames:\n    - chart-assignment-controller.{{ .Release.Namespace }}.svc\n    - chart-assignment-controller.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: ClusterIssuer\n    name: cluster-authority\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: chart-assignment-controller\nspec:\n  type: ClusterIP\n  ports:\n  - port: 443\n    targetPort: webhook\n  selector:\n    app: chart-assignment-controller\n---\n{{ if eq .Values.webhook.enabled \"true\" }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: chart-assignment-controller\n  annotations:\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/chart-assignment-controller\nwebhooks:\n- name: chartassignments.apps.cloudrobotics.com\n  admissionReviewVersions: [\"v1\"]\n  failurePolicy: Fail\n  clientConfig:\n    service:\n      namespace: {{ .Release.Namespace }}\n      name: chart-assignment-controller\n      path: /chartassignment/validate\n  rules:\n  - apiGroups:\n    - apps.cloudrobotics.com\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - chartassignments\n  sideEffects: None\n{{ end }}\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/cert-manager-certificates.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: selfsigned-ca\nspec:\n  isCA: true\n  duration: 8760h # 1year. This needs to be at least 3x 90days\n  commonName: selfsigned-ca\n  secretName: cluster-authority\n  privateKey:\n    algorithm: ECDSA\n    size: 256\n  issuerRef:\n    name: selfsigned-issuer\n    kind: ClusterIssuer\n    group: cert-manager.io\n"
  },
  {
    "path": "src/app_charts/base/robot/cert-manager-issuers.yaml",
    "content": "# A self-signing issuer for cluster-internal services.\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: selfsigned-issuer\nspec:\n  selfSigned: {}\n---\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: cluster-authority\nspec:\n  ca:\n    secretName: cluster-authority\n"
  },
  {
    "path": "src/app_charts/base/robot/cert-manager.yaml",
    "content": "{{ if eq .Values.app_management \"true\" }}\n# This includes all resources expanded from the cert-manager chart using\n# the values in ../cert-manager-robot.values.yaml.\n# Some pseudo-variables that were inserted there are replaced with actual runtime values.\n{{ .Files.Get \"files/cert-manager-chart.robot.yaml\" | replace \"HELM-NAMESPACE\" .Release.Namespace }}\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/cr-syncer.yaml",
    "content": "{{ if eq .Values.cr_syncer \"true\" }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cr-syncer\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: cr-syncer\n  template:\n    metadata:\n      labels:\n        app: cr-syncer\n    spec:\n      containers:\n      - name: cr-syncer\n        image: {{ .Values.registry }}{{ .Values.images.cr_syncer }}\n        args:\n        - --listen-address=:8080\n        - --remote-server={{ .Values.domain }}\n        - --robot-name={{ .Values.robot.name }}\n        - --use-robot-jwt=true\n        - --verbose=false\n        ports:\n        - name: http\n          containerPort: 8080\n        livenessProbe:\n          httpGet:\n            path: /health\n            port: 8080\n          failureThreshold: 3\n          initialDelaySeconds: 10\n          periodSeconds: 120\n          timeoutSeconds: 60\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: cr-syncer\n  labels:\n    app: cr-syncer\nspec:\n  selector:\n    app: cr-syncer\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/fluent-bit.yaml",
    "content": "# !!! DO NOT EDIT THIS FILE !!!\n# This file is autogenerated using src/app_charts/base/fluent-bit-helm.sh.\n# See src/app_charts/base/README.md for update instructions.\n{{ if and (eq .Values.robot_authentication \"true\") (eq .Values.fluentbit \"true\") }}\n---\n# Source: fluent-bit/templates/serviceaccount.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: fluent-bit\n  namespace: default\n  labels:\n    helm.sh/chart: fluent-bit-0.48.9\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n    app.kubernetes.io/version: \"3.2.8\"\n    app.kubernetes.io/managed-by: Helm\n---\n# Source: fluent-bit/templates/configmap.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: fluent-bit\n  namespace: default\n  labels:\n    helm.sh/chart: fluent-bit-0.48.9\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n    app.kubernetes.io/version: \"3.2.8\"\n    app.kubernetes.io/managed-by: Helm\ndata:\n  custom_parsers.conf: |\n    # Merges multi-line Abseil logs. The regexs assume that any line which does not start\n    # with an Abseil log preamble is part of the previous log message. No assumptions on\n    # indentation or similar are made.\n    [MULTILINE_PARSER]\n        Name          absl_logs_multiline\n        Type          regex\n        Flush_timeout 1000\n        #\n        # Regex rules for multiline parsing\n        # ---------------------------------\n        #\n        # configuration hints:\n        #\n        #  - first state always has the name: start_state\n        #  - every field in the rule must be inside double quotes\n        #\n        # rules |   state name  | regex pattern                                                         | next state\n        # ------|---------------|---------------------------------------------------------------------------------------------\n        Rule      \"start_state\"   \"/^((W|I|E|F))([0-9]{4}) ([^ ]+)\\s+([-0-9]+) (\\S+:\\d+)] (.*)$/\"           \"cont\"\n        Rule      \"cont\"          \"/^(?!(((W|I|E|F))([0-9]{4}) ([^ ]+)\\s+([-0-9]+) (\\S+:\\d+)]))(.*)$/\"      \"cont\"\n    \n    # A parser for Abseil log files: https://abseil.io/docs/cpp/guides/logging#prefix\n    [PARSER]\n        Name        absl_logs\n        Format      regex\n        Regex       ^(?<severity>(W|I|E|F))([0-9]{4}) (?<time>[^ ]+)\\s+(?<pid>[-0-9]+) (?<source>\\S+:\\d+)] (?<message>[\\s\\S]*)$\n        Time_Key    time\n        Time_Format %H:%M:%S.%L\n        Time_Keep   On\n        Types       pid:integer\n    \n  fluent-bit.conf: |\n    [SERVICE]\n        Daemon Off\n        Flush 1\n        Log_Level info\n        Parsers_File custom_parsers.conf\n        HTTP_Server On\n        HTTP_Listen 0.0.0.0\n        HTTP_Port 2020\n        Health_Check On\n    \n    [INPUT]\n        Name tail\n        Path /var/log/containers/*.log\n        # Adding the 'absl_logs_multiline' does not work as intended.\n        # It must be specified as a dedicated filter (see below).\n        Multiline.Parser docker, cri, go, python\n        Tag {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Mem_Buf_Limit 5MB\n        Skip_Long_Lines On\n    \n    [INPUT]\n        Name systemd\n        Tag k8s_node.${MY_NODE_NAME}\n        Systemd_Filter  _SYSTEMD_UNIT=containerd.service\n        Systemd_Filter  _SYSTEMD_UNIT=etcd.service\n        Systemd_Filter  _SYSTEMD_UNIT=kube-apiserver.service\n        Systemd_Filter  _SYSTEMD_UNIT=kube-controller-manager.service\n        Systemd_Filter  _SYSTEMD_UNIT=kube-scheduler.service\n        Systemd_Filter  _SYSTEMD_UNIT=kubelet.service\n        Systemd_Filter  _SYSTEMD_UNIT=sshd.service\n        Read_From_Tail On\n    \n    [INPUT]\n        Name systemd\n        Tag kernel.${MY_NODE_NAME}\n        Systemd_Filter _TRANSPORT=kernel\n        Read_From_Tail On\n    \n    [FILTER]\n        Name kubernetes\n        Match {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Merge_Log On\n        Keep_Log Off\n        K8S-Logging.Parser On\n        K8S-Logging.Exclude On\n    \n    # Joins multiple Abseil logs into a single line.\n    [FILTER]\n        Name                    multiline\n        Match                   {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Multiline.Key_content   log\n        Multiline.Parser        absl_logs_multiline\n    \n    # Applies the absl_logs parser to the 'log' field.\n    [FILTER]\n        Name          parser\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Key_Name      log\n        Parser        absl_logs\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_equals level info\n        Add severity INFO\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_equals level warning\n        Add severity WARNING\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_equals level warn\n        Add severity WARNING\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_equals level error\n        Add severity ERROR\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_matches log ^.*\\[\\sinfo\\].*$\n        Add severity INFO\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_matches log ^.*\\[\\swarn\\].*$\n        Add severity WARNING\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_matches log .*\\[error\\].*\n        Add severity ERROR\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_matches log .*(type=\"Info\"|level=info).*\n        Add severity INFO\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_matches log .*(type=\"Warning\"|level=warning).*\n        Add severity WARNING\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_matches log .*(type=\"Error\"|level=error).*\n        Add severity ERROR\n    \n    # We're setting the logName here and below to avoid a high cardinality field on stackdriver\n    # See https://github.com/fluent/fluent-bit/issues/9897\n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_equals stream stderr\n        Add severity ERROR\n        Set logging.googleapis.com/logName stderr\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Condition Key_value_equals stream stdout\n        Add severity INFO\n        Set logging.googleapis.com/logName stdout\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Remove _p\n    \n    [FILTER]\n        Name          modify\n        Match         {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Rename log message\n    \n    [OUTPUT]\n        Name stackdriver\n        Match {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.*\n        Tag_Prefix {{ empty .Values.log_prefix_subdomain | ternary \"\" (print .Values.log_prefix_subdomain \".\" ) -}} kube.var.log.containers.\n        Resource k8s_container\n        k8s_cluster_name {{ .Values.robot.name }}\n        k8s_cluster_location europe-west1-c\n        severity_key severity\n        Workers 1\n    \n    [OUTPUT]\n        Name stackdriver\n        Match k8s_node.*\n        Resource k8s_node\n        custom_k8s_regex ^(?<node_name>.*)$\n        k8s_cluster_name {{ .Values.robot.name }}\n        k8s_cluster_location europe-west1-c\n        Workers 1\n    \n    [OUTPUT]\n        Name stackdriver\n        Match kernel.*\n        Resource k8s_node\n        tag_prefix kernel.\n        custom_k8s_regex ^(?<node_name>.*)$\n        k8s_cluster_name {{ .Values.robot.name }}\n        k8s_cluster_location europe-west1-c\n        Workers 1\n---\n# Source: fluent-bit/templates/clusterrole.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: fluent-bit\n  labels:\n    helm.sh/chart: fluent-bit-0.48.9\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n    app.kubernetes.io/version: \"3.2.8\"\n    app.kubernetes.io/managed-by: Helm\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - namespaces\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n---\n# Source: fluent-bit/templates/clusterrolebinding.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: fluent-bit\n  labels:\n    helm.sh/chart: fluent-bit-0.48.9\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n    app.kubernetes.io/version: \"3.2.8\"\n    app.kubernetes.io/managed-by: Helm\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: fluent-bit\nsubjects:\n  - kind: ServiceAccount\n    name: fluent-bit\n    namespace: default\n---\n# Source: fluent-bit/templates/service.yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: fluent-bit\n  namespace: default\n  labels:\n    helm.sh/chart: fluent-bit-0.48.9\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n    app.kubernetes.io/version: \"3.2.8\"\n    app.kubernetes.io/managed-by: Helm\nspec:\n  type: ClusterIP\n  ports:\n    - port: 2020\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n---\n# Source: fluent-bit/templates/daemonset.yaml\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: fluent-bit\n  namespace: default\n  labels:\n    helm.sh/chart: fluent-bit-0.48.9\n    app.kubernetes.io/name: fluent-bit\n    app.kubernetes.io/instance: fluent-bit\n    app.kubernetes.io/version: \"3.2.8\"\n    app.kubernetes.io/managed-by: Helm\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: fluent-bit\n      app.kubernetes.io/instance: fluent-bit\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: fluent-bit\n        app.kubernetes.io/instance: fluent-bit\n      annotations:\n        checksum/config: 62f3260bfeab5914c8b602bc04f3e1aa7bd3fe9691a18590d746d3ddc09fd432\n    spec:\n      serviceAccountName: fluent-bit\n      hostNetwork: false\n      dnsPolicy: ClusterFirst\n      containers:\n        - name: fluent-bit\n          image: \"cr.fluentbit.io/fluent/fluent-bit:3.2.8\"\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: MY_NODE_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n          command:\n            - /fluent-bit/bin/fluent-bit\n          args:\n            - --workdir=/fluent-bit/etc\n            - --config=/fluent-bit/etc/conf/fluent-bit.conf\n          ports:\n            - name: http\n              containerPort: 2020\n              protocol: TCP\n          livenessProbe:\n            httpGet:\n              path: /\n              port: http\n          readinessProbe:\n            httpGet:\n              path: /api/v1/health\n              port: http\n          volumeMounts:\n            - name: config\n              mountPath: /fluent-bit/etc/conf\n            - mountPath: /var/log\n              name: varlog\n            - mountPath: /run/log\n              name: runlog\n            - mountPath: /etc/machine-id\n              name: etcmachineid\n              readOnly: true\n      volumes:\n        - name: config\n          configMap:\n            name: fluent-bit\n        - hostPath:\n            path: /var/log\n          name: varlog\n        - hostPath:\n            path: /run/log\n          name: runlog\n        - hostPath:\n            path: /etc/machine-id\n            type: File\n          name: etcmachineid\n      tolerations:\n        - effect: NoSchedule\n          key: rtpc\n          operator: Exists\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/fluentd-gcp-addon.yaml",
    "content": "{{ if and (eq .Values.robot_authentication \"true\") (eq .Values.fluentd \"true\") }}\n{{ .Files.Get \"files/fluentd-gcp-configmap.yaml\" }}\n---\n{{ .Files.Get \"files/fluentd-gcp-ds.yaml\" | replace \"/var/lib/docker\" .Values.docker_data_root }}\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/fluentd-metrics.yaml",
    "content": "# Adds a Prometheus ServiceMonitor for scraping the fluentd metrics.\n# By default, google-fluentd exports some Prometheus metrics on port 24231.\n{{ if and (eq .Values.robot_authentication \"true\") (eq .Values.fluentd \"true\") }}\napiVersion: v1\nkind: Service\nmetadata:\n  name: fluentd-metrics\n  labels:\n    app: fluentd-metrics\nspec:\n  ports:\n  - port: 24231\n    name: metrics\n  selector:\n    k8s-app: fluentd-gcp\n  type: ClusterIP\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: fluentd-metrics\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n  - port: metrics\n    path: /metrics\n    interval: 10s\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n  selector:\n    matchLabels:\n      app: fluentd-metrics\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/gcr-credential-refresher.yaml",
    "content": "{{ if and (eq .Values.robot_authentication \"true\") (ne .Values.project \"\") (eq .Values.running_on_gke \"false\") }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gcr-credential-refresher\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gcr-credential-refresher\n  template:\n    metadata:\n      labels:\n        app: gcr-credential-refresher\n    spec:\n      containers:\n      - image: {{ .Values.registry }}{{ .Values.images.gcr_credential_refresher }}\n        args:\n        - --robot_id_file=/credentials/robot-id.json\n        - --service_account={{ .Values.robot.defaultSAName }}\n        name: gcr-credential-refresher\n        resources:\n          requests:\n            cpu: \"1m\"\n            memory: \"50Mi\"\n          limits:\n            cpu: \"10m\"\n            memory: \"200Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n        volumeMounts:\n        - mountPath: /credentials\n          name: robot-id\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n      volumes:\n      - name: robot-id\n        secret:\n          secretName: robot-auth\n          items:\n          - key: json\n            path: robot-id.json\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/robot/metadata-server.yaml",
    "content": "{{ if and (eq .Values.robot_authentication \"true\") (ne .Values.project \"\") }}\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: metadata-server\nspec:\n  selector:\n    matchLabels:\n      name: metadata-server\n  template:\n    metadata:\n      labels:\n        name: metadata-server\n    spec:\n      hostNetwork: true\n      volumes:\n      - name: robot-id\n        secret:\n          secretName: robot-auth\n          items:\n          - key: json\n            path: robot-id.json\n      # Mounting /etc/ssl is necessary if ca-certificates is not installed in the container image.\n      - name: ssl\n        hostPath:\n          path: /etc/ssl\n      containers:\n      - name: metadata-server\n        image: {{ .Values.registry }}{{ .Values.images.metadata_server }}\n        args:\n        - --bind_ip=127.0.0.1\n        - --port=8965\n        - --robot_id_file=/credentials/robot-id.json\n        - --source_cidr={{ .Values.pod_cidr }}\n        - --running_on_gke={{ .Values.running_on_gke }}\n        - --service_account={{ .Values.robot.defaultSAName }}\n        securityContext:\n          readOnlyRootFilesystem: true\n          capabilities:\n            drop:\n              - all\n            add:\n              - NET_ADMIN\n              - NET_RAW\n        volumeMounts:\n        - mountPath: /credentials\n          name: robot-id\n        - mountPath: /etc/ssl\n          name: ssl\n      # This daemon-set need to run on all nodes for auth to work.\n      tolerations:\n      - operator: \"Exists\"\n        effect: \"NoSchedule\"\n      priorityClassName: system-cluster-critical\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/base/values-cloud.yaml",
    "content": "domain: \"example.com\"\ningress_ip: \"\"\nproject: \"my-gcp-project\"\nregion: \"us-north1-a\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\nowner_email: \"info@example.com\"\n\n# Setting app_management to \"false\" will remove layer 2 (app-rollout/chart-assignment-controller, etc).\napp_management: \"true\"\n# Setting onprem_federation to \"false\" will remove cloud policy for the cr-syncer (part of layer 1).\nonprem_federation: \"true\"\n\noauth2_proxy:\n  cookie_secret: \"\"\n  client_id: \"\"\n  client_secret: \"\"\n\n# Certificate provider\n# This configures different cert-manager templates to emit certificates using the chosen authority.\ncertificate_provider: \"\"\n\n# Certificate authority used to sign in-cluster certificates.\ncertificate_authority:\n  key: \"\"\n  crt: \"\"\n"
  },
  {
    "path": "src/app_charts/base/values-robot.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\n\n# Setting app_management to \"false\" will remove layer 2 (app-rollout/chart-assignment-controller, etc).\napp_management: \"true\"\n\n# Setting cr_syncer to \"false\" will remove the cr-syncer.\ncr_syncer: \"true\"\n\n# Setting fluentd to \"false\" will remove the fluentd stackdriver integration.\nfluentd: \"true\"\n\n# Setting fluentbit to \"true\" will enable the fluentbit stackdriver integration.\nfluentbit: \"false\"\n\n# Setting log_prefix_subdomain to any non-empty string will prepend a subdomain to the\n# `Tag_Prefix` configured on fluentbit (don't add a \".\" at the end to the subdomain)\nlog_prefix_subdomain: \"\"\n\n# docker_data_root should match \"data-root\" in /etc/docker/daemon.json.\ndocker_data_root: \"/var/lib/docker\"\n\n# Setting robot_authentication to \"false\" will remove layer 1 resources that are not\n# needed when simulating a robot with a GKE cluster, such as the\n# metadata-server or gcr-credential-refresher.\nrobot_authentication: \"true\"\n\n# If running on GKE, skip setup steps that are unnecessary and will fail.\nrunning_on_gke: \"false\"\n\nrobot:\n  name: \"\"\n  # Name of the default GCP Service Account used by robot when connecting to cloud.\n  defaultSAName: \"robot-service\"\n\nwebhook:\n  enabled: \"true\"\n"
  },
  {
    "path": "src/app_charts/k8s-relay/BUILD.bazel",
    "content": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"k8s-relay-cloud\",\n    images = {\"http-relay-server\": \"//src/go/cmd/http-relay-server:http-relay-server-image\"},\n    values = \"values-cloud.yaml\",\n)\n\napp_chart(\n    name = \"k8s-relay-robot\",\n    images = {\"http-relay-client\": \"//src/go/cmd/http-relay-client:http-relay-client-image\"},\n    values = \"values-robot.yaml\"\n)\n\napp(\n    name = \"k8s-relay\",\n    charts = [\n        \":k8s-relay-cloud\",\n        \":k8s-relay-robot\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: kubernetes-relay-client\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify\"\n    nginx.ingress.kubernetes.io/client-body-buffer-size: \"50m\"\n    nginx.ingress.kubernetes.io/proxy-body-size: \"50m\"\n    # proxy-read-timeout sets how long nginx will allow a request to be idle\n    # for. This is important for requests like `kubectl logs -f` where the logs\n    # may be silent for some time.\n    nginx.ingress.kubernetes.io/proxy-read-timeout: \"86400\"\n    nginx.ingress.kubernetes.io/rewrite-target: /client/$2\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /apis/core.kubernetes-relay/client($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: kubernetes-relay-server\n            port:\n              number: 80\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: kubernetes-relay-server\n  annotations:\n    nginx.ingress.kubernetes.io/client-body-buffer-size: \"50m\"\n    nginx.ingress.kubernetes.io/proxy-body-size: \"50m\"\n    nginx.ingress.kubernetes.io/rewrite-target: /server/$2\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /apis/core.kubernetes-relay/server($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: kubernetes-relay-server\n            port:\n              number: 80\n"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/kubernetes-relay-server.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: kubernetes-relay-server\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: kubernetes-relay-server\n  template:\n    metadata:\n      labels:\n        app: kubernetes-relay-server\n    spec:\n      containers:\n      - name: kubernetes-relay-server\n        image: {{ .Values.registry }}{{ .Values.images.http_relay_server }}\n        args:\n        - --log_level=4 # WARN\n        - --port=8080\n        env:\n        # Enable tracebacks for debugging deadlocks or hanging requests.\n        - name: GOTRACEBACK\n          value: all\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8080\n          initialDelaySeconds: 15\n          timeoutSeconds: 10\n        ports:\n        - name: http\n          containerPort: 8080\n        resources:\n{{- toYaml .Values.kubernetes_relay_server.resources | nindent 10 }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n\n"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/service-monitor.yaml",
    "content": "apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: kubernetes-relay-server\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n  - port: http\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n    {{- if .Values.prometheus.serviceMonitor.metricRelabelings }}\n    metricRelabelings: {{- tpl (toYaml .Values.prometheus.serviceMonitor.metricRelabelings | nindent 6) . }}\n    {{- end }}\n  selector:\n    matchLabels:\n      app: kubernetes-relay-server\n"
  },
  {
    "path": "src/app_charts/k8s-relay/cloud/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: kubernetes-relay-server\n  labels:\n    # This is used by the ServiceMonitor.\n    app: kubernetes-relay-server\nspec:\n  ports:\n  - port: 80\n    targetPort: 8080\n    protocol: TCP\n    name: http\n  selector:\n    app: kubernetes-relay-server\n  type: ClusterIP\n"
  },
  {
    "path": "src/app_charts/k8s-relay/robot/kubernetes-relay-client.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: kubernetes-relay-client\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: kubernetes-relay-client\n  template:\n    metadata:\n      labels:\n        app: kubernetes-relay-client\n    spec:\n      containers:\n      - name: kubernetes-relay-client\n        image: {{ .Values.registry }}{{ .Values.images.http_relay_client }}\n        args:\n        - --log_level=4 # WARN\n        - --backend_address=$(KUBERNETES_SERVICE_HOST):$(KUBERNETES_SERVICE_PORT)\n        - --backend_scheme=https\n        - --authentication_token_file=/var/run/secrets/kubernetes.io/serviceaccount/token\n        - --root_ca_file=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        - --relay_address={{ .Values.domain }}\n        - --relay_prefix=/apis/core.kubernetes-relay\n        - --server_name={{ .Values.robot.name }}\n        - --disable_http2\n        resources:\n{{- toYaml .Values.kubernetes_relay_client.resources | nindent 10 }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n\n"
  },
  {
    "path": "src/app_charts/k8s-relay/values-cloud.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\n\n# MetricRelabelConfigs to apply to samples after scraping, but before ingestion.\nprometheus:\n  serviceMonitor:\n    metricRelabelings:  []\n\nkubernetes_relay_server:\n  resources:\n    requests:\n      memory: \"16Mi\"\n      cpu: \"100m\"\n"
  },
  {
    "path": "src/app_charts/k8s-relay/values-robot.yaml",
    "content": "kubernetes_relay_client:\n  resources:\n    requests:\n      memory: \"10Mi\"\n      cpu: \"50m\"\n"
  },
  {
    "path": "src/app_charts/mission-crd/BUILD.bazel",
    "content": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"mission-crd-robot\",\n    extra_templates = [\"mission_crd.yaml\"],\n    values = \"values.yaml\",\n)\n\napp_chart(\n    name = \"mission-crd-cloud\",\n    extra_templates = [\"mission_crd.yaml\"],\n    values = \"values.yaml\",\n)\n\napp(\n    name = \"mission-crd\",\n    charts = [\n        \":mission-crd-cloud\",\n        \":mission-crd-robot\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/mission-crd/mission_crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cr-syncer.cloudrobotics.com/filter-by-robot-name: \"true\"\n    cr-syncer.cloudrobotics.com/spec-source: cloud\n  name: missions.mission.cloudrobotics.com\nspec:\n  group: mission.cloudrobotics.com\n  names:\n    kind: Mission\n    plural: missions\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        properties:\n          spec:\n            properties:\n              actions:\n                items:\n                  properties:\n                    charge:\n                      properties:\n                        charger_name:\n                          type: string\n                        target_battery_percent:\n                          description: |-\n                            If actually performing the charge action, charge until this level is\n                            reached.\n                          format: int64\n                          type: integer\n                        threshold_battery_percent:\n                          description: |-\n                            Only perform the charge action if battery level is lower than this\n                            threshold, otherwise do nothing.\n                          format: int64\n                          type: integer\n                      type: object\n                    get_trolley:\n                      properties:\n                        dock_name:\n                          description: |-\n                            Name of dock to get trolley from, eg \"1\". This should correspond to\n                            the name of the mission created by ROEQ's Create_docking_station.exe, eg\n                            \"ROEQ_Get cart 1\".\n                          type: string\n                      type: object\n                    move_to_named_position:\n                      properties:\n                        target_name:\n                          description: String id of target position as created in\n                            MiR's web frontend.\n                          type: string\n                      type: object\n                    return_trolley:\n                      properties:\n                        dock_name:\n                          description: |-\n                            Name of dock to return trolley to, eg \"1\". This should correspond to\n                            the name of the mission created by ROEQ's Create_docking_station.exe, eg\n                            \"ROEQ_Return cart 1\".\n                          type: string\n                      type: object\n                  type: object\n                type: array\n              time_out_sec:\n                format: float\n                type: number\n            type: object\n          status:\n            properties:\n              active_action:\n                properties:\n                  id:\n                    title: The ID of the currently executed action\n                    type: string\n                  index:\n                    format: int64\n                    title: The index of the currently executed action\n                    type: integer\n                  status:\n                    description: |2-\n                       - DEFAULT: Used if no other specific status applies.\n                       - DOCKING: Robot is currently docking.\n                       - MOVING: Robot is moving.\n                       - TIMEOUT: Mission duration exceeded MissionSpec.time_out_sec. Can only be set if\n                      MissionStatus.status is FAILED.\n                    enum:\n                    - DEFAULT\n                    - DOCKING\n                    - MOVING\n                    - TIMEOUT\n                    type: string\n                title: Information about the currently executed action within a mission\n                type: object\n              message:\n                type: string\n              queue_entry_id:\n                description: Links the goal to the entry in the MiR's mission queue.\n                format: int64\n                type: string\n              status:\n                description: |-\n                  The various states of the mission execution.\n\n                   - CREATED: initial state\n                   - ACCEPTED: mission has been validated on\n                   - RUNNING: active state (processing)\n                   - SUCCEEDED: terminal states\n                enum:\n                - CREATED\n                - ACCEPTED\n                - RUNNING\n                - SUCCEEDED\n                - CANCELED\n                - FAILED\n                type: string\n              time_of_actuation:\n                format: date-time\n                type: string\n            type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions:\n  - v1alpha1\n"
  },
  {
    "path": "src/app_charts/mission-crd/values.yaml",
    "content": "project: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\n\nrobot:\n  name: \"\"\n\ncrd_spec_source: \"cloud\"\n"
  },
  {
    "path": "src/app_charts/platform-apps/BUILD.bazel",
    "content": "load(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"platform-apps-cloud\",\n    extra_templates = [\n        \"//src/app_charts:app_resources\",\n    ],\n    values = \"values.yaml\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/platform-apps/values.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\ndeploy_environment: \"GCP\"\nregistry: \"gcr.io/my-gcp-project\"\n"
  },
  {
    "path": "src/app_charts/prometheus/BUILD.bazel",
    "content": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\nload(\"//bazel:build_rules/helm_template.bzl\", \"helm_template\")\n\nhelm_template(\n    name = \"prometheus-operator-chart.cloud\",\n    chart = \"//third_party/kube-prometheus-stack:kube-prometheus-stack-72.9.1.tgz\",\n    helm_version = 3,\n    # The namespace will later be replaced with the actual one.\n    namespace = \"HELM-NAMESPACE\",\n    # Pick a short release name as it will be used as a prefix for a lot of resources.\n    release_name = \"prom\",\n    values = \"prometheus-cloud.values.yaml\",\n)\n\nhelm_template(\n    name = \"prometheus-operator-chart.robot\",\n    chart = \"//third_party/kube-prometheus-stack:kube-prometheus-stack-72.9.1.tgz\",\n    helm_version = 3,\n    # The namespace will later be replaced with the actual one.\n    namespace = \"HELM-NAMESPACE\",\n    # Pick a short release name as it will be used as a prefix for a lot of resources.\n    release_name = \"prom\",\n    values = \"prometheus-robot.values.yaml\",\n)\n\napp_chart(\n    name = \"prometheus-cloud\",\n    extra_templates = [\n        \"//third_party/kube-prometheus-stack:00-crds.yaml\",\n    ],\n    files = [\n        \":prometheus-operator-chart.cloud\",\n    ],\n    images = {\n        \"http-relay-server\": \"//src/go/cmd/http-relay-server:http-relay-server-image\",\n    },\n    values = \"values-cloud.yaml\",\n)\n\napp_chart(\n    name = \"prometheus-robot\",\n    extra_templates = [\n        \"//third_party/kube-prometheus-stack:00-crds.yaml\",\n    ],\n    files = [\n        \":prometheus-operator-chart.robot\",\n    ],\n    images = {\n        \"http-relay-client\": \"//src/go/cmd/http-relay-client:http-relay-client-image\",\n        \"hw-exporter\": \"//src/go/cmd/hw-exporter:hw-exporter-image\",\n    },\n)\n\napp(\n    name = \"prometheus\",\n    charts = [\n        \":prometheus-cloud\",\n        \":prometheus-robot\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/prometheus/README.md",
    "content": "# Prometheus App\n\nThe Prometheus app uses the upstream Helm chart\n`prometheus-community/prometheus-operator` as a basis for cloud and robot chart\nalike.\n\nThe folowing caveats exist that explain some choices made:\n\n* The prometheus-operator chart is vendored in `third_party/` as Helm\n  packaging does not generate reproducible artifacts that can be downloaded and\n  verified via file checksums at build time.\n* Helm cannot template `values.yaml` files themselves. For example, we need to\n  provide an external URL for Prometheus, which must include the project domain\n  name. But it is only provided at deploy time as `.Values.domain`.\n  For this reason, we expand the prometheus-operator chart at build time\n  and insert pseudo-variables, which are string-replaced by Helm at deploy time\n  during template processing (see `cloud/prometheus-operator.yaml`).\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/app.yaml",
    "content": "apiVersion: app.k8s.io/v1beta1\nkind: Application\nmetadata:\n  name: \"prometheus\"\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n    app.kubernetes.io/version: {{ .Chart.Version }}\n  annotations:\n    kubernetes-engine.cloud.google.com/icon: >-\n      data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAACXBIWXMAAAsSAAALEgHS3X78AAAVI0lEQVR42u1dCZQVxRX9w8CwySqIBBFxj4NM9x9BEdSAosBMdw+gbDFRQYyiOVFxQcWgGARxIwrqcd+TqBBNIAuQGNfEuEQ4Eaa7B7dIUFwBRQwy5L3u+p72+5f+/3f/qeX1OXfOEWequ17dV/Wq6i2JBD2RPY5Rm3CsZIVj6W0cU+8M2BdQCzgBcDJgKuA8wEWAywCzGS5j/3Ye+52T2d/UsjawLWgT29ZI0PS0MNHrjkrYplYJpNwbMBRwFuA6wBOAVwAbAdsBzYDdJaKZtbWRtb0McD1757H4DbapVzaNTdLA0BPP45p6BRBtT8AowBzAU4C3AF9FQPBSgd/wDmA54CrAGNv71mQFjRw9xc3wlkf4HoCxgMWANYCvOSB7WOC3rmXfPh7Q0zU0Ugh6cpAezAjAQMClgOeY2bFbEnwJeB5wuddHQ6+kEacHSY8z/eGAeYA3BJvli8UuwHrAtYAaMJVaERPUsueR+H0AM5lp06wA6XNtsNFUuhiwD6wMZCZJPNvj0eRIwFLJzJsozSQ8YToJZUWMkYf4nQAzmInTTEQPtSqsA/wUZUcMEpH0Y5IJdkaPx4KbidRF40PANYDe68fWErF4f96YUI3E/x5gIeAzInBk2AK4AfdOTQ01RDROTZ3u7DTnM4FMjQ2AfwikCCjb+Y6ldyfG8UP8DoCfseVaFCK95m3IjdpW7FZZRNPofJQ9MbCliG/pSJ56tmETaYN5B6A9U95qdi4vqmmE9wn1tqnTXUKZZ/3+gCcFI08z25tUBvpxrSQXazgW/YmZ8RO/CnAhYKuARHnYtZLfkN/2L+RelmijvA0vF20z2ZaYGg/50U/nBUHJsS79TN0xtfaSnlThGA0kxkZGfK0123B9LighdgKGZVDoXpy4U8eBz9lK3ZoYXNqs3xewUnAy3OeYNZn61hvwP8nvD3Ds+hKTC3xsy7OP6wCbJJgJ+2dR7i4Cr2qF4H1/LCmUM+ysj05rcyWZHe9uMjIPvFufbMUiuVS4Sf4fG1NysstDfgw7XCGRU1nWzaBb5/krLVfMpeIPGGlHTM9M/mp2sSLLYL/kGLnjclmElmp+RTjG1cT4bxNhpIRemxeF6Pcghb1MTyTi+yGJpwF2SBh2eGjezb7hpVb5j6JKgGN+OmbcUNSXR2vFAtB3Sji4b4bd8MHv3aKwmzWO/SxHNV8i1/ROQBYI7giWC4+59VrYVXBIjJFq7woQ/okcWGCbWitVzJ7WCsx6swuUxxsxfMO/AT8SSGa3SH9zzAZ7iQKxuVMKlMusiN//DvOYXSLYsfFt6Poip9ljeWbPLYoEpo8oUAH2ZVkZotpcHm37ib4aBbw7uVW6PYHj2/wLFdrcDS5EPo2mdym2LKJ3Xx7wNRL1dG2hNErAjjovk3jDmwl6EXIaHcF7X4WZvx1rb5jgx8iXu4YECX6hI9MUSTkYxICC5WQkW7Pj01IS4h4fkPsUwWWI/ZkmOvlHC+TzvoYRsDmC2eugIuV1RQnvXRFMiQ7/fa4EEwlyZ7So5McIro8FEvbjgLZ4O1nid6PnY58iZdavyM0wKt3RaW3NlGQ1/Vi4CDP44L0ArmCC3hzI1oAZo98rsp0vAHsUeUFYUWSalOc2pCW2ZVF0spiUyKW9BLnl1TBwfZWgGy8tQKAky4hWaDubSvF7h781i3jnpAztTJdsX7XKsfQqzsnvnfiIfNw5KY1EZxa5lyj69MK19PYFOshtyrTiwL9ZEh4uLOTaeY6V4xHZue2CNIXG+4vVhQZ9RCDH6wt435IsbdRKeOmI3BrHK/n3B3wkuIDnZOhXssDwzCUly9LQB4QkL/7OMVnGo4ekdRAwlmB/3siPdv+zEgj3qvS+NZlaBTshCtvGhRFdHr4WztszcyIqx/Bu3xsdOe9ZgGvJKp4UYLYkgr04S/+GFnCTPSYimV4U4l135WnjUfK4jZ/8gyN05GppnJGlj+hY9krINvaLSK79Qlwijs3Txk8kL+k0uGXJb3gnFq9JJNQROch0QUj7NJLUH65vej2Xx+tzzzwKcLDkPlivpe5uWmr2nyNZ7pp+eWbkfCdcT0cs35/leNfLeZXI8C7W1kmsABkPLspF/sMkMn12s5vfNnn6/M88bSyIWMb75TiBujFkG/MkVwDk4GHl9u9Hm/gvkgny9yHINDdPGw3RytlLHJDNxBwXUgE0BbxxVwfrLpRj9p8s4SXLJSH6PTyPTb53DLJelOVCqF/Iv68MsXLJkIVvcrnI30XSnJa1IfrejRWCyBKMkmwVg7wnZon5bVNAG2cpEIPxNnKTNr7FYaNj5M9I4PibymxVXebHJO9DSnW3YJPWBwoowZy4yd/HEbM8UT7cU4AM7s6yBB8bk8zbMhfr4PuuK6KdaxRQAOTm92IhP8vZf5ukgqsv8WgSE1DFUhvLrtcSGfIGnVpkjIYKxcNvQ67GMRMdLKlzFQbCdCpADidlaGNxzCvv8rTVJllkOyqsAtuLDUnNdeyZbemXAfd/MuHwQm3y9GPFI2NWgCVpA9yjyHa6efsd+ZXgLjfKijSoUZJdegVn0xMLlEVX59uljdbEncMmLXPcu24JZ96S+wcFL8cOjGsGkglou7crzEfHS+8YjNg6uwwnbz8OvO9vEWyqX1dACRZHJfxejrzF3G4uUiZrAuGIXcqgACcGTbYI2hujQIpK5GwvOvfPbf7UFimTVJjkrDJdPg4MfPfVJZ8sjR2I9xkr6V4gv+A7OvJWMFnrWMWl3oO/XYo3j7DR6lwmBegdcGueGlGbRzlyFicJArnbsRQhTZFYOJeWIJcHyuZ74r+vUyBFy6go2mSu0k8psApMKW6Z9DM6PyOpUHaUErkFf1tXzvQcjr/xTiXqGhhhu0cqsAo84xbjo4UJXiV2o12JEVcJQR7HT8/yOhuP3lG165o1KuwFvi4mWXFC8nz+P04I9GwYOwDH4094CWabNW0jVq7RCphB1xUoFK1NCbkxeceWfLG0nK4C96HbRtTtuoYXL7BecgV4z7YKiNWGPzhBYmE8kRDwge++Fp3iYmr7EgVWgeMLEcg9EgviFEEVYAZmiYip7X0d+YqVF5RDKSgMTHXyvrz+4slOgipAHXqFxtF2U72XiW6V5AqwCUz79qqbP8sSgj4sC8dDMa8wZAbBL/1SYgGcIbACdMas0TG2f2CBSYBFxKLcQrC8Cxdb0s5jmsF9BFYAvAs4Pa72G/32GyVXgMachbjhFw6VOJXeq04MWRvKrAQHxNz+nZIrAHL7kFwCOFfizt+YEPxpNI+IW8HOVGAfMCOXAJZJ3HEjQU8+BdAVUIClWex/rVLi3DE7YkuXIZcCdFLgPuD9jCGl8D+qJe50k2vpFUTxUEqwQYFV4DDV7L+lRO3QCrBaAQWYlqnjd0nc4blE7dAKcK8CCnBneqcr/GNCaTt8KlE7tAIsUEABXnWMQDwI2/xslbjDxxC1QyvATEVyiO4R7LQmcZqM5rgvkCRTgOkKKAByoibY6ckSd3anK2AATAsqwGQFFAAxKdhpmZOm7ogrc7OkCjBOEQXwD0ZcP+35r6TOE9mgtSJqh1YAUxEF+JVtVX9zAiRzDakvN1hJUoDwClCviAIg5ytSAfD/ldkEajQ1MoHIBEoHcr5NKt23zP4fO4vNp6+oApymiAIg57umKr80S37kdRBRO7QCXKCIAvi8gB/DKBaUnoAC3KiIAiCGYYfHK9DRs4naoRVgqUIKME6Vm79bidqhFWCdQgpwJnb4YgU6+tIrwwcQu/OTv4sCATFBXKRK6cxtjqF3I4rnVYDjFCK/fxsMP25QpLOjiOJ5FWCOYgpwg8zV39OxmCie/XGNGuTCi4opwBIVcsGk8DY5xeWc/fs6ctaCzh0ZpkgIXOri4wSielYFOE8x8iPuUUkBEL8mqmcgv6G1ktwhMhvulT0Y/ruu0QLnB41x9j9S4pSYuWsGwI/bFev0dUT57yjAIwqSH3E7dv5mxTr9qRNhpUUJyH8wy5ytogLchAKYx+Fm9cuYPVQXNY2vpaNPyzv6fLBMY7qNQ6/jeSiAWZx9FNZ1HQIYzKqW3A94I+LiDdszpsdTb/YfHENRjGaWduQFvHsBTAUkWeJd3opyz0IhnM17JjfX1FOBOyOYwj4J2OiUVsh7ta1wrDBGQwGej2Cy+hDwLDOlf+TnmNWqbFNLf9+FXHoJw4+JHH6YDQKsyjWAth/KifbrFPT2ZMd4nxf4nul07l8Q2TGMEIvpzWe8OTjM5SIWJuH0lnligs2qPG5QCrq0cqzaCrZKHAu4lK0S7+c53vs0Z8UQuTe+W/NUUsEVdjngahYnvB+gyqkbVMz7qjk0fxDDUx/HY0jkb5qsI0ocaG/mweJvPwTcAvg74LM0pXgZ0FEh8ndkfU7foL6CvjGYRxUz6W0wkxURvpPHKDPsdzV+XA9OtRP90vvGQIA92IZsGlMKtINvbLKS0tcPcCyvEN4iwOuABwA/ZSGxXV1jYFwKh3lnP+I2WQL8aMc2MjyaQQviJsWGuhrYTyTbwYZYfgUwk5WAzq5ZvmIhDr915zZ7+xfb0tB2XsvpR34C6EnXVYLeM/iTq8Mpt9a6aOY5hnfE+CTHt3XXNMVcHZGe2Gb/Mzjm1W9dS/vmQ3mOCsOTmj5EJ7Ee29Q7cDz7I64Pauo03r323AaNWCXW7H8+55yaGvzYIQKkNyQ7SBzy92SbTJ45NSR4PNZDgHC4F21MZkoP3+Q3kiLEmSPX9wwqAJ4ENQrgvgrLag2xjO/Zf0gMDnZRo9FOrxuN4YICKMAWFV0XBCJ/e8C/RCiOkenjRckK/Ixr6q2Jbnw9600vtuBqQTh0fralS5RInivIFOJu9h8qgOmTwlEZNi/erd02gYLbhxHtuCF/d87P/L9VI9g1tXbf6USjMRA78heBVoENVPml5R/X0iphHB4TiDers3oZ+6aFUEHNv3NMjfYDLfQ0+UeeswTjzOW5lrIjBYzsn20bFOBe7mdd/YBUSdWdgvFlcC4FwH3AJsE6hBsvgyhZdru/lvlpiVYZsl2OTnlL2kMCrgKfUJaHspL/IMB7AvLkQadBz9u5iQJ2bDe7yT7ANfWehPiAF5ECnfikY0LYI61tgnZwJ8tyRogPOwXlBiYB6J5fAfxCCU8pmiqPIC8w6Cu0jTeFBEaQDJML2eR0ypM3hkAQy/yx9E6F7vQfIMERJMH9xRx1DSfBESTBD4pRAMy9uZ6ERxAc65xiIwnhD2eSAAmCY2YpN369BNoMY77P0x1TO5kQK053xKknhhGEvUpQgKRIdYS/ti29GzkqxOwGYWjdS6zLUE7cmdf1IcQqUC1Ih0kBSAHS6xlUR+D4pIlyM0wKQAoQxFOuEVHYLEuhvYsUgB5BFGBXpCGztp9X/s+kAPQIogDI1Wjrv0GDx3G+CpACkAKkZv9jI+84FlWAhleQApACcK4AK1wzpmInjl/v9WtSAFIAjk9+krF13q7Xy1VZnBSAFKAYPODGnTCNlcvcSgpACsBhxNd+sQugaeRQVIIrSQFIAXhLj/PmCUPLJAS/1KhNCkAKwAmAi8k9yisIUx/t8FVgmxRATQVADo4uuyDcBq+86qOkAKQALYxHG40WKnIOL+/NUSY5UgD1FAC517tlBeJnkGjm5AZwBuA0QqyYwYlHAHJuSovPCLbhmUKPUOQRocx4xGkp0+c7+wHTqzL5Dg0KoUx42wlWeeTCNvSzSHxFg0OIGcix4dxtjlw/cOZKGiBCzLjSNnVOTwhMrQ3nHqMEsbHC4b1YOnzgXo5fv4sGjBB1Tbi9xDgr9t2mtwgqaDxe2yEpmgUdky2xujnHpASnOOLUjQ1iLZhyPeQE9k248UAOnSLejaGpi1hBEOFKe4sLfRNwPGY5vG56854MWUm8JFsimMA/c8xklWzkt029yu+bUGOxxK7XK0SfdVDwjwu2B+gv4ezfX7A9wOO2qVXJIvyOgJUCCX+ChAowQSD5r0LOyDYAWHXmWUEG4G4JFeBuQWT/HKCzrJuwboDnBRiE/8o0A7EVWIQC6C84YSo5Cj4Y3QVZCSZKJPNJgsz8cpM/zRxaxfmAvARoLYGsWwP+ybmsV0tr9uRZlp/g/DRoggRynsj56c9STLKQUPGBjrcF3M6533kXgeXbhfWBV/negRxIqPy4poaZp68A7OR0kO6JPNNwWcifRLney6lMcaxnO5Z4co3nltLSUxs1HjPOYfzr9EYrKRD5a1Ge0znN5o1jPBnIT8QPPk+fMwIHbRDgLQ4HbTtglECmzyj2zTyalIN395lOhM8xeL04vTXeBhjpNAzmVnZNY7yVdCT7Vt7kh2O6NzE8nBKg/9A1HLpTf46pOJrqa7hz0LLNmgrPtPC/kTd7H8eyiphdmBIgTgS8x+GALgAblpsBdQxvwpjP4UHCRjTHbEMjQhd/SuSZRE9weJb9ImCg09Byg2tbNThJHM6+hbc7lKUlFammJ2DbWt6R3hmADzkb6C8ACwE9WmCF3JO9+wvOZPIRYKptaXTEGe2Ae2lX9gUs43CDtxkwpxybPHZI8HPABxzK4bc4Rq9PGkGEjVERcLM3jtMbzu0sTSQeQ3aIkPQdACcBHuJwxt/Njq7H22ayghhaPhOgK9v4bef0thNn6McA5wBq0dnLGZufIPg73u/6mTXOBvya09k+pfDzbUvvSoxsib1BQxIV4VC2Sea5hnEzuwG12Xn4w4DFgBsYFrN/W8V+ZyvnDmy72Cb30Ka4i9LRE2Y18GbNYwBPC5z7RpR46WewELVr6GTucGgWVQLGsMgiUoRoiY9HrXWuqVUS03i/O7A8RcCN6F9JESKZ8XFSIeILtyL4xTuOYhvJL4nQobGDbeCHNDaQqSPLHQLmxZkLeJcInhXodvILwP6ukSTiSHdqNG4QKkJ7QAPgSSro8U3Bid+xu5UOTeMHEVHUMI/0VCr3GSxLxVeKkR77fB7eXtsmBafQMaqp92WXVss5jUyLIp7hj0zh+7mwP6KRpyeTMqRumfHk4ybAGo7jlXPWTWbp0BcB6v0+kVsyPYUqhKVXMM9LEzCP5bDZzFm19F3sm/7qxSr439oTQLM8PdE+tr93wFpoBwIswGXMSe0FllLxq5juHppZ2/iOfzDXiSvYhv4g10xW4bfRQ0/5V4k673QpYftFAjH/6WGA41mI4vns+PVWwP3sfP33zB5fyfBH9m+Psd+5lf3NBYBTWZzv97FtfIcLM7s7lsgexfN/McuIGxiL1x0AAAAASUVORK5CYII=\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ .Chart.Name }}\n  componentKinds:\n  - group: core\n    kind: Service\n  - group: apps\n    kind: Deployment\n  - group: apps\n    kind: Ingress\n  - group: apps\n    kind: StatefulSet\n  descriptor:\n    type: \"prometheus\"\n    version: {{ .Chart.Version }}\n    description: \"Prometheus provides metrics and alerting\"\n    keywords:\n    - \"dashboard\"\n    - \"metric\"\n    links:\n    - description: Prometheus\n      url: \"https://{{ .Values.domain }}/prometheus/\"\n    - description: Grafana Dashboard\n      url: \"https://{{ .Values.domain }}/grafana/\"\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/base-alerts.yaml",
    "content": "apiVersion: monitoring.coreos.com/v1\nkind: PrometheusRule\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n    prometheus: kube-prometheus\n    role: alert-rules\n  name: base-alerts\nspec:\n  groups:\n  - name: base-alerts.rules\n    rules:\n    - alert: CloudRoboticsPodFrequentlyRestarting\n      expr: increase(kube_pod_container_status_restarts_total[1h]) > 5\n      for: 10m\n      labels:\n        severity: warning\n      annotations:\n        description: Pod {{`{{$labels.namespace}}`}}/{{`{{$labels.pod}}`}} was restarted {{`{{$value}}`}}\n          times within the last hour\n        logs: \"https://console.cloud.google.com/logs/viewer?project={{ .Values.project }}&resource=container&logName=projects%2F{{ .Values.project }}%2Flogs%2F{{`{{$labels.container}}`}}&interval=PT6H\"\n        summary: Pod is restarting frequently\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/federation-service-monitor.yaml",
    "content": "{{ range .Values.robots }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: prometheus-federation-{{ .name }}\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n  - port: http\n    path: /client/{{ .name }}/federate\n    params:\n      'match[]':\n      # Identified via Prometheus query: topk(10, count by (__name__)({__name__=~\".+\"}))\n      # As of 2021-07-21 this config reduces the number of time series scraped by approx 1/4\n      - '{__name__=~\".+\", __name__!~\"apiserver_request_duration_seconds_bucket|apiserver_request_slo_duration_seconds_bucket|etcd_request_duration_seconds_bucket|apiserver_response_sizes_bucket\"}'\n    honorLabels: true\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n  # These are the labels below on the federation service\n  targetLabels:\n  - robot\n  - cluster\n  selector:\n    matchLabels:\n      app: prometheus-federation\n      robot: \"{{ .name }}\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: prometheus-federation-{{ .name }}\n  labels:\n    app: prometheus-federation\n    robot: \"{{ .name }}\"\n    cluster: \"{{ .name }}\"\nspec:\n  ports:\n  - port: 80\n    targetPort: 8080\n    protocol: TCP\n    name: http\n  selector:\n    app: prometheus-relay-server\n  type: ClusterIP\n---\n{{ end }}\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/grafana-ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: grafana\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"{{ tpl .Values.gf_ingress_auth_url . }}\"\n    nginx.ingress.kubernetes.io/auth-signin: \"{{ tpl .Values.gf_ingress_auth_signin . }}\"\n    nginx.ingress.kubernetes.io/backend-protocol: HTTP\n    nginx.ingress.kubernetes.io/rewrite-target: /$2\n    # HACK: oauth2-proxy will return 403, but nginx-ingress-controller only handles\n    # 401 with an error page.\n    nginx.ingress.kubernetes.io/configuration-snippet: |\n      error_page 403 = {{ tpl .Values.gf_ingress_error_page_403 . }};\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /grafana($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: prom-grafana\n            port:\n              number: 80\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/prometheus-ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: prometheus\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"{{ tpl .Values.prom_ingress_auth_url . }}\"\n    nginx.ingress.kubernetes.io/auth-signin: \"{{ tpl .Values.prom_ingress_auth_signin . }}\"\n    nginx.ingress.kubernetes.io/backend-protocol: HTTP\n    nginx.ingress.kubernetes.io/rewrite-target: /$2\n    nginx.ingress.kubernetes.io/configuration-snippet: |\n      error_page 403 = {{ tpl .Values.prom_ingress_error_page_403 . }};\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /prometheus($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: kube-prometheus\n            port:\n              number: 9090\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/prometheus-operator.yaml",
    "content": "# This includes all resources expanded from the prometheus-operator chart using\n# the values in ../prometheus-cloud.values.yaml.\n# Some pseudo-variables that were inserted there are replaced with actual runtime values.\n# TODO(rodrigoq): This severely limits how the end-user can customize the\n# prometheus deployment. How could we let them override prometheus-cloud.values.yaml?\n# NOTE: The order here is important. The domain and project might be part of other values and\n#       need to be replaced last.\n{{- $data := .Files.Get \"files/prometheus-operator-chart.cloud.yaml\" -}}\n# Pre-process variables\n{{- $data = $data | replace \"${CR_GF_SERVER_DOMAIN}\" .Values.gf_server_domain | replace \"${CR_GF_SERVER_ROOT_URL}\" .Values.gf_server_root_url | replace \"${CR_GF_CSRF_TRUSTED_ORIGINS}\" .Values.gf_csrf_trusted_origins | replace \"${CR_GF_SMTP_ENABLED}\" .Values.gf_smtp_enabled | replace \"${CR_GF_SMTP_HOST}\" .Values.gf_smtp_host | replace \"${CR_GF_SMTP_USER}\" .Values.gf_smtp_user | replace \"${CR_GF_SMTP_PASSWORD}\" .Values.gf_smtp_password | replace \"${CR_GF_SMTP_FROM_ADDRESS}\" .Values.gf_smtp_from_address | replace \"${CR_GF_SMTP_FROM_NAME}\" .Values.gf_smtp_from_name | replace \"${CR_GF_SMTP_SKIP_VERIFY}\" .Values.gf_smtp_skip_verify | replace \"${CR_GF_INGRESS_AUTH_URL}\" .Values.gf_ingress_auth_url | replace \"${CR_GF_INGRESS_AUTH_SIGNIN}\" .Values.gf_ingress_auth_signin | replace \"${CR_GF_INGRESS_ERROR_PAGE_403}\" .Values.gf_ingress_error_page_403 | replace \"${CR_PROM_INGRESS_AUTH_URL}\" .Values.prom_ingress_auth_url | replace \"${CR_PROM_INGRESS_AUTH_SIGNIN}\" .Values.prom_ingress_auth_signin | replace \"${CR_PROM_INGRESS_ERROR_PAGE_403}\" .Values.prom_ingress_error_page_403 | replace \"HELM-NAMESPACE\" .Release.Namespace | replace \"${LIMITS_MEMORY}\" .Values.limits.memory | replace \"${LIMITS_CPU}\" .Values.limits.cpu | replace \"${REQUESTS_STORAGE}\" .Values.requests.storage | replace \"${RETENTION_TIME}\" .Values.retention.time | replace \"${RETENTION_SIZE}\" .Values.retention.size | replace \"${EXTERNAL_URL}\" .Values.prom_external_url | replace \"${CLOUD_ROBOTICS_DOMAIN}\" .Values.domain | replace \"${GCP_PROJECT_ID}\" .Values.project -}}\n\n# Inject the nodeSelector as a pre-formatted YAML block to allow users to define multiple selectors in a dict within values-cloud.yaml while maintaining valid indentation in the output.\n{{- $prometheusNodeSelectorMap := .Values.prometheus.prometheusSpec.nodeSelector -}}\n{{- if $prometheusNodeSelectorMap }}\n  {{- $prometheusNodeSelectorRawYaml := toYaml $prometheusNodeSelectorMap -}}\n  {{- $formattedPrometheusNodeSelectorObject := $prometheusNodeSelectorRawYaml | nindent 6 -}}\n  {{- $data = $data | replace \"${CR_PROMETHEUS_NODE_SELECTOR_OBJECT}\" $formattedPrometheusNodeSelectorObject -}}\n{{- else }}\n  {{- $data = $data | replace \"${CR_PROMETHEUS_NODE_SELECTOR_OBJECT}\" \" {}\" -}}\n{{- end }}\n\n# Inject the tolerations as a pre-formatted YAML block to support multiple key-value pairs in a list within values-cloud.yaml\n{{- $prometheusTolerationsList := .Values.prometheus.prometheusSpec.tolerations -}}\n{{- if $prometheusTolerationsList }}\n  {{- $prometheusTolerationsRawYaml := toYaml $prometheusTolerationsList -}}\n  {{- $formattedTolerationsObject := $prometheusTolerationsRawYaml | nindent 6 -}}\n  {{- $data = $data | replace \"${CR_PROMETHEUS_TOLERATIONS_OBJECT}\" $formattedTolerationsObject -}}\n{{- else }}\n  {{- $data = $data | replace \"${CR_PROMETHEUS_TOLERATIONS_OBJECT}\" \" []\" -}}\n{{- end }}\n\n{{ $data }}\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/prometheus-relay.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus-relay-server\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus-relay-server\n  template:\n    metadata:\n      labels:\n        app: prometheus-relay-server\n    spec:\n      containers:\n      - name: prometheus-relay-server\n        image: {{ .Values.registry }}{{ .Values.images.http_relay_server }}\n        args:\n        - --log_level=4 # WARN\n        - --port=8080\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8080\n          initialDelaySeconds: 15\n        ports:\n        - name: http\n          containerPort: 8080\n        resources:\n          requests:\n            memory: \"16Mi\"\n            cpu: \"100m\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: prometheus-relay-server\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n    # This is used by the ServiceMonitor.\n    app: prometheus-relay-server\nspec:\n  ports:\n  - port: 80\n    targetPort: 8080\n    protocol: TCP\n    name: http\n  selector:\n    app: prometheus-relay-server\n  type: ClusterIP\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: prometheus-relay-server\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n  annotations:\n    nginx.ingress.kubernetes.io/client-body-buffer-size: \"50m\"\n    nginx.ingress.kubernetes.io/proxy-body-size: \"50m\"\n    nginx.ingress.kubernetes.io/rewrite-target: /server/$2\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: /apis/core.prometheus-relay/server($|/)(.*)\n        pathType: Prefix\n        backend:\n          service:\n            name: prometheus-relay-server\n            port:\n              number: 80\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: prometheus-relay-server\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n  - port: http\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n    {{- if .Values.prometheus.serviceMonitor.metricRelabelings }}\n    metricRelabelings: {{- tpl (toYaml .Values.prometheus.serviceMonitor.metricRelabelings | nindent 6) . }}\n    {{- end }}\n  selector:\n    matchLabels:\n      app: prometheus-relay-server\n"
  },
  {
    "path": "src/app_charts/prometheus/cloud/storage-class.yaml",
    "content": "apiVersion: storage.k8s.io/v1\nkind: StorageClass\nmetadata:\n  name: ssd\n  labels:\n    app.kubernetes.io/name: {{ .Chart.Name }}\nprovisioner: kubernetes.io/gce-pd\nparameters:\n  type: pd-ssd\nallowVolumeExpansion: true\n\n"
  },
  {
    "path": "src/app_charts/prometheus/prometheus-cloud.values.yaml",
    "content": "# Configuration for the prometheus-operator chart.\n# Reference: \n# https://github.com/prometheus-community/helm-charts/blob/kube-prometheus-stack-41.5.1/charts/kube-prometheus-stack/values.yaml\n#\n# WARNING: the prometheus-operator chart is complicated and error-prone. If you\n# edit this file, run the following command to generate the output with `helm\n# template`, and verify that your changes have the expected effect.\n#\n#   bazel build src/app_charts/prometheus/prometheus-operator-chart.cloud.yaml\n\nnameOverride: kube\nfullnameOverride: kube\n\nkubeTargetVersionOverride: \"1.23.8\"\n\n# Alertmanagers have to be deployed individually by users.\nalertmanager:\n  enabled: false\n\ndefaultRules:\n  rules:\n    kubeApiserver: false\n\nprometheus:\n  prometheusSpec:\n    # Pick up all service monitors across all namespaces.\n    serviceMonitorNamespaceSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n    serviceMonitorSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n\n    # Pick up all pod monitors across all namespaces.\n    podMonitorNamespaceSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n    podMonitorSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n\n    # Pick up all rules across all namespaces.\n    ruleNamespaceSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n    ruleSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n\n    externalUrl: \"${EXTERNAL_URL}\"\n    retention: \"${RETENTION_TIME}\"\n    retentionSize: \"${RETENTION_SIZE}\"\n    walCompression: true\n    nodeSelector: \"${CR_PROMETHEUS_NODE_SELECTOR_OBJECT}\"\n    tolerations: \"${CR_PROMETHEUS_TOLERATIONS_OBJECT}\"\n    resources:\n      requests:\n        cpu: \"${LIMITS_CPU}\"\n        memory: \"${LIMITS_MEMORY}\"\n      limits:\n        cpu: \"${LIMITS_CPU}\"\n        memory: \"${LIMITS_MEMORY}\"\n    storageSpec:\n      volumeClaimTemplate:\n        spec:\n          storageClassName: ssd\n          accessModes: [\"ReadWriteOnce\"]\n          resources:\n            requests:\n              storage: \"${REQUESTS_STORAGE}\"\n    # Pick up user-created Alertmanager pods with app=alertmanager and a non-empty port.\n    additionalAlertManagerConfigs:\n    - kubernetes_sd_configs:\n      - role: service\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n      authorization:\n        type: Bearer\n        credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      relabel_configs:\n      - source_labels: [__meta_kubernetes_service_label_app]\n        regex: kube-alertmanager\n        action: keep\n    # Set absurdly high thresholds as a workaround for not being able to disable these and not having enough time to WAL replay\n    # https://github.com/prometheus-operator/prometheus-operator/issues/3587\n    containers:\n    - name: prometheus\n      readinessProbe:\n        initialDelaySeconds: 300\n        failureThreshold: 100000\n  serviceMonitor:\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\n# etcd, scheduler, and controller-manager are managed by GKE and hidden.\nkubeEtcd:\n  enabled: false\nkubeControllerManager:\n  enabled: false\nkubeScheduler:\n  enabled: false\ncoreDns:\n  enabled: false\n\n# Throws an invalid namespace \"kube-system\" error during deployment, as this is\n# trying to install resources into the kube-system namespace, which synk does\n# not support.\nkubeProxy:\n  enabled: false\n\nprometheusOperator:\n  admissionWebhooks:\n    enabled: true\n    certManager:\n      enabled: true\n      issuerRef:\n        name: \"selfsigned-issuer\"\n        kind: \"ClusterIssuer\"\n  serviceMonitor:\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\n\n# Default scraping interval is 20s and these metrics result in a large amount of data\nkubeApiServer:\n  serviceMonitor:\n    interval: 1m\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\nkubelet:\n  serviceMonitor:\n    # From kubernetes 1.18, /metrics/resource/v1alpha1 renamed to /metrics/resource\n    resourcePath: \"/metrics/resource\"\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n    - sourceLabels: [__metrics_path__]\n      targetLabel: metrics_path\n\nkubeStateMetrics:\n  serviceMonitor:\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\n# Subcharts\n\nnodeExporter:\n  enabled: true\n  serviceMonitor:\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\ngrafana:\n  env:\n    GF_SERVER_DOMAIN: \"${CR_GF_SERVER_DOMAIN}\"\n    GF_SERVER_ROOT_URL: \"${CR_GF_SERVER_ROOT_URL}\"\n    GF_AUTH_ANONYMOUS_ENABLED: \"true\"\n  # Load dashboards from configmaps with a given label across all namespaces.\n  sidecar:\n    dashboards:\n      enabled: true\n      label: grafana # Label our own legacy grafana-operator uses.\n      searchNamespace: ALL\n      multicluster:\n        global:\n          enabled: true\n        etcd:\n          enabled: true\n  grafana.ini:\n    analytics:\n      check_for_updates: false\n    security:\n      csrf_trusted_origins: \"${CR_GF_CSRF_TRUSTED_ORIGINS}\"\n    smtp:\n      enabled: \"${CR_GF_SMTP_ENABLED}\"\n      host: \"${CR_GF_SMTP_HOST}\"\n      user: \"${CR_GF_SMTP_USER}\"\n      password: \"${CR_GF_SMTP_PASSWORD}\"\n      from_address: \"${CR_GF_SMTP_FROM_ADDRESS}\"\n      from_name: \"${CR_GF_SMTP_FROM_NAME}\"\n      skip_verify: \"${CR_GF_SMTP_SKIP_VERIFY}\"\n  serviceMonitor:\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\ngf_ingress_auth_url: \"${CR_GF_INGRESS_AUTH_URL}\"\ngf_ingress_auth_signin: \"${CR_GF_INGRESS_AUTH_SIGNIN}\"\ngf_ingress_error_page_403: \"${CR_GF_INGRESS_ERROR_PAGE_403}\"\n\nprom_ingress_auth_url: \"${CR_PROM_INGRESS_AUTH_URL}\"\nprom_ingress_auth_signin: \"${CR_PROM_INGRESS_AUTH_SIGNIN}\"\nprom_ingress_error_page_403: \"${CR_PROM_INGRESS_ERROR_PAGE_403}\"\n"
  },
  {
    "path": "src/app_charts/prometheus/prometheus-robot.values.yaml",
    "content": "# Configuration for the prometheus-operator chart.\n# Reference:\n# https://github.com/prometheus-community/helm-charts/blob/kube-prometheus-stack-15.4.6/charts/kube-prometheus-stack/values.yaml\n#\n# WARNING: the prometheus-operator chart is complicated and error-prone. If you\n# edit this file, run the following command to generate the output with `helm\n# template`, and verify that your changes have the expected effect.\n#\n#   bazel build src/app_charts/prometheus/prometheus-operator-chart.robot.yaml\n\nnameOverride: kube\nfullnameOverride: kube\n\nkubeTargetVersionOverride: \"1.23.8\"\n\nalertmanager:\n  enabled: false\n\ndefaultRules:\n  create: false\n\nprometheus:\n  prometheusSpec:\n    # Pick up all service monitors across all namespaces.\n    serviceMonitorNamespaceSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n    serviceMonitorSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n\n    # Pick up all pod monitors across all namespaces.\n    podMonitorNamespaceSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n    podMonitorSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n\n    # Pick up all rules across all namespaces.\n    ruleNamespaceSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n    ruleSelector:\n      # Inverse selector selects everything\n      matchExpressions:\n      - key: \"non-existent-label-for-universal-matching\"\n        operator: \"DoesNotExist\"\n\n    logLevel: warn\n    # Historical data is limited to the lower of `retention` and\n    # `retentionSize`. WAL+maxChunkSize also counted toward retentionSize,\n    # but Prometheus puts no limit on WAL size, so it's up to us to make\n    # sure we don't have too many metrics and the WAL doesn't grow too big.\n    retention: \"3h\"\n    # If you increase retentionSize, increase sizeLimit below as well, but\n    # remember that this is RAM, not disk. We don't know how much headroom\n    # is needed, but if set equal you can still run out of disk space.\n    retentionSize: 448MB\n    # Reduce the max chunk size (default=512MB) to reduce the headroom required\n    # for the in-progress chunk.\n    # This chunk size correlates with the current retention size. Larger\n    # retention sizes make it so that WAL is not truncated as it should be.\n    additionalArgs:\n      - name: storage.tsdb.max-block-chunk-segment-size\n        value: 16MB\n      - name: storage.tsdb.wal-segment-size\n        value: 16MB\n    storageSpec:\n      emptyDir:\n        medium: Memory\n        sizeLimit: 512Mi\n  serviceMonitor:\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\n# Throws an invalid namespace \"kube-system\" error during deployment, as this is\n# trying to install resources into the kube-system namespace, which synk does\n# not support.\nkubeEtcd:\n  enabled: false\nkubeControllerManager:\n  enabled: false\nkubeProxy:\n  enabled: false\nkubeScheduler:\n  enabled: false\ncoreDns:\n  enabled: false\n\nprometheusOperator:\n  admissionWebhooks:\n    enabled: true\n    certManager:\n      enabled: true\n      issuerRef:\n        name: \"selfsigned-issuer\"\n        kind: \"ClusterIssuer\"\n  serviceMonitor:\n    metricRelabelings:\n    # Drop high cardinality kube state metrics\n    - action: drop\n      regex: \"kube_*\"\n      sourceLabels: [ __name__ ]\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\n# Default scraping interval is 20s and these metrics result in a large amount of data\nkubeApiServer:\n  serviceMonitor:\n    interval: 10m\n    metricRelabelings:\n    # Drop high cardinality apiserver metrics.\n    - action: drop\n      regex: \"apiserver_(request|response|watch|admission).*|etcd_request.*|code_*\"\n      sourceLabels: [__name__]\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\nkubelet:\n  serviceMonitor:\n    # From kubernetes 1.18, /metrics/resource/v1alpha1 renamed to /metrics/resource\n    resourcePath: \"/metrics/resource\"\n    metricRelabelings:\n    # Drop high cardinality metrics from kubelet.\n    - action: drop\n      regex: \"container_network.*|container_blkio.*|kubelet_.*|kubernetes_feature_enabled|container_fs.*|container_processes|container_last_seen|storage_operation_duration_seconds_bucket\"\n      sourceLabels: [__name__]\n    cAdvisorMetricRelabelings:\n    # Drop high cardinality metrics from kubelet. (with metrics_path=\"/metrics/cadvisor\")\n    - sourceLabels: [__name__]\n      action: drop\n      regex: 'container_cpu_(cfs_throttled_seconds_total|load_average_10s|system_seconds_total|user_seconds_total)'\n    - sourceLabels: [__name__]\n      action: drop\n      regex: 'container_fs_(io_current|io_time_seconds_total|io_time_weighted_seconds_total|reads_merged_total|sector_reads_total|sector_writes_total|writes_merged_total)'\n    - sourceLabels: [__name__]\n      action: drop\n      regex: 'container_memory_(mapped_file|swap|failures_total)'\n    - sourceLabels: [__name__]\n      action: drop\n      regex: 'container_(file_descriptors|tasks_state|threads_max)'\n    - sourceLabels: [__name__]\n      action: drop\n      regex: 'container_spec.*|container_network.*'\n    - sourceLabels: [id, pod]\n      action: drop\n      regex: '.+;'\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n    - sourceLabels: [__metrics_path__]\n      targetLabel: metrics_path\n\n# Subcharts\n\nnodeExporter:\n  enabled: true\n  serviceMonitor:\n    metricRelabelings:\n    # Drop high cardinality metrics from node exporter.\n    - action: drop\n      regex: \"node_cpu_seconds_total\"\n      sourceLabels: [ __name__ ]\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n\nprometheus-node-exporter:\n  extraArgs:\n    # This collector produces log-spam on newer kernels\n    # https://github.com/prometheus/node_exporter/issues/1892\n    - --no-collector.rapl\n    # Since we have hardware network IRQs, this generates 7 zero-value metrics for each CPU core.\n    - --no-collector.softnet\n    # This is disabled by default, since it might leak memory\n    # (https://github.com/prometheus/node_exporter/blob/master/CHANGELOG.md#0160-rc1--2018-04-04)\n    - --collector.wifi\n    # Export CPU model (one metric per core)\n    - --collector.cpu.info\n    # Ignore more fuse filesystems\n    # https://github.com/prometheus/node_exporter/blob/master/collector/filesystem_linux.go#L33\n    - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|fuse\\.\\w*|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$\n    # Ignore filesystems with UIDs in the mount points (high cardinality)\n    - --collector.filesystem.mount-points-exclude=^(/run/containerd/|/var/lib/kubelet)\n    # Ignore virtual network devices\n    - --collector.netdev.device-exclude=^(bond|cilium|ip6tnl0|lo|lxc|tunl)\n    - --collector.netclass.ignored-devices=^(bond|cilium|ip6tnl0|lo|lxc|tunl)\n\ngrafana:\n  enabled: false\n"
  },
  {
    "path": "src/app_charts/prometheus/robot/hw-exporter.yaml",
    "content": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: hw-exporter\nspec:\n  selector:\n    matchLabels:\n      app: hw-exporter\n  template:\n    metadata:\n      labels:\n        app: hw-exporter\n    spec:\n      containers:\n        - name: hw-exporter\n          image: {{ .Values.registry }}{{ .Values.images.hw_exporter }}\n          args:\n            - --metrics-port=9100\n            - --chroot=/host\n          volumeMounts:\n          - mountPath: /host/proc\n            name: proc\n            readOnly: true\n          - mountPath: /host/sys\n            name: sys\n            readOnly: true\n          - mountPath: /host/usr/share\n            name: usr-share\n            readOnly: true\n      securityContext:\n        fsGroup: 65534\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n      tolerations:\n        - operator: Exists\n          effect: NoSchedule\n      volumes:\n      - hostPath:\n          path: /proc\n          type: \"\"\n        name: proc\n      - hostPath:\n          path: /sys\n          type: \"\"\n        name: sys\n      # Mount pcidb from host, which could be in /usr/share/misc or /usr/share/hwdata.\n      - hostPath:\n          path: /usr/share\n          type: \"\"\n        name: usr-share\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hw-exporter\n  labels:\n    app: hw-exporter\nspec:\n  clusterIP: None\n  ports:\n    - port: 9100\n      name: http-metrics\n  selector:\n    app: hw-exporter\n  type: ClusterIP\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: hw-exporter\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n    - port: http-metrics\n      path: /metrics\n      interval: 60s\n  selector:\n    matchLabels:\n      app: hw-exporter\n"
  },
  {
    "path": "src/app_charts/prometheus/robot/prometheus-adapter.yaml",
    "content": "apiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: v1beta1.metrics.k8s.io\nspec:\n  group: metrics.k8s.io\n  groupPriorityMinimum: 100\n  insecureSkipTLSVerify: true\n  service:\n    name: prometheus-adapter\n    namespace: {{ .Release.Namespace }}\n  version: v1beta1\n  versionPriority: 100\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n    rbac.authorization.k8s.io/aggregate-to-admin: \"true\"\n    rbac.authorization.k8s.io/aggregate-to-edit: \"true\"\n    rbac.authorization.k8s.io/aggregate-to-view: \"true\"\n  name: system:aggregated-metrics-reader\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - metrics.k8s.io\n  resources:\n  - pods\n  - nodes\n  verbs:\n  - get\n  - list\n  - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: resource-metrics:system:auth-delegator\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: hpa-controller-custom-metrics\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: custom-metrics-server-resources\nsubjects:\n- kind: ServiceAccount\n  name: horizontal-pod-autoscaler\n  namespace: kube-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: prometheus-adapter\nsubjects:\n- kind: ServiceAccount\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: resource-metrics-server-resources\nrules:\n- apiGroups:\n  - metrics.k8s.io\n  resources:\n  - '*'\n  verbs:\n  - '*'\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - nodes\n  - namespaces\n  - pods\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n---\napiVersion: v1\ndata:\n  config.yaml: |-\n    \"resourceRules\":\n      \"cpu\":\n        \"containerLabel\": \"container\"\n        \"containerQuery\": |\n          sum by (<<.GroupBy>>) (\n            irate (\n                container_cpu_usage_seconds_total{<<.LabelMatchers>>,container!=\"\",pod!=\"\"}[4m]\n            )\n          )\n        \"nodeQuery\": |\n          sum by (<<.GroupBy>>) (\n            irate(\n                node_cpu_usage_seconds_total{<<.LabelMatchers>>}[4m]\n            )\n          )\n        \"resources\":\n          \"overrides\":\n            \"namespace\":\n              \"resource\": \"namespace\"\n            \"node\":\n              \"resource\": \"node\"\n            \"pod\":\n              \"resource\": \"pod\"\n      \"memory\":\n        \"containerLabel\": \"container\"\n        \"containerQuery\": |\n          sum by (<<.GroupBy>>) (\n            container_memory_working_set_bytes{<<.LabelMatchers>>,container!=\"\",pod!=\"\"}\n          )\n        \"nodeQuery\": |\n          sum by (<<.GroupBy>>) (\n            node_memory_working_set_bytes{<<.LabelMatchers>>}\n          )\n        \"resources\":\n          \"overrides\":\n            \"node\":\n              \"resource\": \"node\"\n            \"namespace\":\n              \"resource\": \"namespace\"\n            \"pod\":\n              \"resource\": \"pod\"\n      \"window\": \"5m\"\nkind: ConfigMap\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: adapter-config\n  namespace: {{ .Release.Namespace }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/component: metrics-adapter\n      app.kubernetes.io/name: prometheus-adapter\n  strategy:\n    rollingUpdate:\n      maxSurge: 1\n      maxUnavailable: 1\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/component: metrics-adapter\n        app.kubernetes.io/name: prometheus-adapter\n        app.kubernetes.io/version: 0.12.0\n    spec:\n      automountServiceAccountToken: true\n      containers:\n      - args:\n        - --cert-dir=/var/run/serving-cert\n        - --config=/etc/adapter/config.yaml\n        - --metrics-relist-interval=1m\n        - --prometheus-url=http://kube-prometheus.{{ .Release.Namespace }}.svc:9090/\n        - --secure-port=6443\n        - --tls-cipher-suites=TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA\n        image: registry.k8s.io/prometheus-adapter/prometheus-adapter:v0.12.0\n        livenessProbe:\n          failureThreshold: 5\n          httpGet:\n            path: /livez\n            port: https\n            scheme: HTTPS\n          initialDelaySeconds: 30\n          periodSeconds: 5\n        name: prometheus-adapter\n        ports:\n        - containerPort: 6443\n          name: https\n        readinessProbe:\n          failureThreshold: 5\n          httpGet:\n            path: /readyz\n            port: https\n            scheme: HTTPS\n          initialDelaySeconds: 30\n          periodSeconds: 5\n        resources:\n          requests:\n            cpu: 102m\n            memory: 180Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /tmp\n          name: tmpfs\n          readOnly: false\n        - mountPath: /var/run/serving-cert\n          name: volume-serving-cert\n          readOnly: false\n        - mountPath: /etc/adapter\n          name: config\n          readOnly: false\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext: {}\n      serviceAccountName: prometheus-adapter\n      volumes:\n      - emptyDir: {}\n        name: tmpfs\n      - emptyDir: {}\n        name: volume-serving-cert\n      - configMap:\n          name: adapter-config\n        name: config\n---\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\nspec:\n  egress:\n  - {}\n  ingress:\n  - {}\n  podSelector:\n    matchLabels:\n      app.kubernetes.io/component: metrics-adapter\n      app.kubernetes.io/name: prometheus-adapter\n  policyTypes:\n  - Egress\n  - Ingress\n---\napiVersion: policy/v1\nkind: PodDisruptionBudget\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\nspec:\n  minAvailable: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/component: metrics-adapter\n      app.kubernetes.io/name: prometheus-adapter\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: resource-metrics-auth-reader\n  namespace: kube-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\n---\napiVersion: v1\nautomountServiceAccountToken: false\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n    app.kubernetes.io/version: 0.12.0\n  name: prometheus-adapter\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - name: https\n    port: 443\n    targetPort: 6443\n  selector:\n    app.kubernetes.io/component: metrics-adapter\n    app.kubernetes.io/name: prometheus-adapter\n"
  },
  {
    "path": "src/app_charts/prometheus/robot/prometheus-operator.yaml",
    "content": "# This includes all resources expanded from the prometheus-operator chart using\n# the values in ../prometheus-cloud.values.yaml.\n# Some pseudo-variables that were inserted there are replaced with actual runtime values.\n{{ .Files.Get \"files/prometheus-operator-chart.robot.yaml\" | replace \"${CLOUD_ROBOTICS_DOMAIN}\" .Values.domain | replace \"${GCP_PROJECT_ID}\" .Values.project | replace \"HELM-NAMESPACE\" .Release.Namespace }}\n"
  },
  {
    "path": "src/app_charts/prometheus/robot/prometheus-relay-client.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus-relay-client\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus-relay-client\n  template:\n    metadata:\n      labels:\n        app: prometheus-relay-client\n    spec:\n      containers:\n      - args:\n        - --log_level=4 # WARN\n        - --backend_address=kube-prometheus.{{ .Release.Namespace }}.svc.cluster.local:9090\n        - --backend_scheme=http\n        - --relay_address={{ .Values.domain }}\n        - --relay_prefix=/apis/core.prometheus-relay\n        - --server_name={{ .Values.robot.name }}\n        image: {{ .Values.registry }}{{ .Values.images.http_relay_client }}\n        name: prometheus-relay-client\n        resources:\n          requests:\n            cpu: 20m\n            memory: 100Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n\n"
  },
  {
    "path": "src/app_charts/prometheus/robot/smartctl-exporter.yaml",
    "content": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: smartctl-exporter\nspec:\n  selector:\n    matchLabels:\n      app: smartctl-exporter\n  template:\n    metadata:\n      labels:\n        app: smartctl-exporter\n    spec:\n      containers:\n        - name: smartctl-exporter\n          # Mirrored from quay.io/prometheuscommunity/smartctl-exporter\n          image: gcr.io/cloud-robotics-releases/smartctl-exporter:v0.12.0\n          args:\n            - --web.listen-address=:9633\n          securityContext:\n            privileged: true\n            runAsUser: 0\n      tolerations:\n        - operator: Exists\n          effect: NoSchedule\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: smartctl-exporter\n  labels:\n    app: smartctl-exporter\nspec:\n  clusterIP: None\n  ports:\n    - port: 9633\n      name: http-metrics\n  selector:\n    app: smartctl-exporter\n  type: ClusterIP\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: smartctl-exporter\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n    - port: http-metrics\n      path: /metrics\n      interval: 60s\n  selector:\n    matchLabels:\n      app: smartctl-exporter\n"
  },
  {
    "path": "src/app_charts/prometheus/update_prometheus_adapter.sh",
    "content": "#!/bin/bash\n\nVERSION=\"0.12.0\"\nOUT=\"robot/prometheus-adapter.yaml\"\nwget https://github.com/kubernetes-sigs/prometheus-adapter/archive/refs/tags/v\"${VERSION}\".tar.gz\ntar xvzf v\"${VERSION}\".tar.gz\n\nawk 'FNR==1 && NR>1 {print \"---\"}{print}' \"prometheus-adapter-${VERSION}/deploy/manifests/\"*.yaml > \"${OUT}\"\nsed -i 's#replicas: 2#replicas: 1#g' \"${OUT}\"\nsed -i 's#namespace: monitoring#namespace: {{ .Release.Namespace }}#g' \"${OUT}\"\nsed -i 's#https://prometheus.monitoring.svc#http://kube-prometheus.{{ .Release.Namespace }}.svc#g' \"${OUT}\"\n\n"
  },
  {
    "path": "src/app_charts/prometheus/values-cloud.yaml",
    "content": "domain: \"example.com\"\nproject: \"my-gcp-project\"\nregistry: \"gcr.io/my-gcp-project\"\nrobots: []\n\n# The default requests/limits are sufficient for small deployments with a few\n# robots. For a large deployment with ~30 robots, you might need ~2CPU and\n# ~12Gi mem.\n# TODO(rodrigoq): can we reduce this by updating prometheus?\nlimits:\n  cpu: \"2000m\"\n  memory: \"2Gi\"\n\n# The default persistent disk size. You need to adjust this defeping your fleet\n# size and desired retention time.\n#\n# To compute the disk space required we used the formula in\n# https://devops.stackexchange.com/questions/9298/how-to-calculate-disk-space-required-by-prometheus-v2-2\n#\n# retention_time_seconds = 90 * 24 * 60 * 60\n# ingested_samples_per_second = avg(sum(rate(prometheus_tsdb_head_samples_appended_total[1d]))) = ~7000\n# bytes_per_sample = avg(sum(rate(prometheus_tsdb_compaction_chunk_size_bytes_sum[1d]) /\n#                            rate(prometheus_tsdb_compaction_chunk_samples_sum[1d]))) = ~1.0\n# needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample = ~72G\n# Use a larger volume to account for future growth.\nrequests:\n  storage: \"200Gi\"\nretention:\n  time: \"90d\"\n  # Keep in sync with the disksize above and keep some headroom to avoid alerts.\n  # Retention size should be at most 80-85% of total storage to account for  WAL, chunks, and filesystem overhead.\n  size: \"160GB\"\n\n# MetricRelabelConfigs to apply to samples after scraping, but before ingestion.\nprometheus:\n  prometheusSpec:\n    nodeSelector: {}\n    tolerations: []\n  serviceMonitor:\n    metricRelabelings: []\n\n# Grafana HTTP configuration\ngf_server_domain: \"${CLOUD_ROBOTICS_DOMAIN}\"\ngf_server_root_url: \"https://${CLOUD_ROBOTICS_DOMAIN}/grafana\"\ngf_csrf_trusted_origins: \"\"\n# Grafana Ingress configuration. Does not use the same replace as the values above.\ngf_ingress_auth_url: \"http://oauth2-proxy.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify\"\ngf_ingress_auth_signin: \"https://{{ .Values.domain }}/oauth2/start?rd=$escaped_request_uri\"\ngf_ingress_error_page_403: \"https://{{ .Values.domain }}/oauth2/start?rd=$escaped_request_uri\"\n# Prometheus\nprom_ingress_auth_url: \"http://oauth2-proxy.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify\"\nprom_ingress_auth_signin: \"https://{{ .Values.domain }}/oauth2/start?rd=$escaped_request_uri\"\nprom_ingress_error_page_403: \"https://{{ .Values.domain }}/oauth2/start?rd=$escaped_request_uri\"\n# Prometheus: Using ${} replacement\nprom_external_url: \"https://${CLOUD_ROBOTICS_DOMAIN}/prometheus/\"\n# Grafana SMTP configuration\n# Notes: these need to be all string, since we apply them using the template funtion \"replace\"\ngf_smtp_enabled: \"false\"\ngf_smtp_host: \"smtp-host\"\ngf_smtp_user: \"smtp-user\"\ngf_smtp_password: \"smtp-api-key\"\ngf_smtp_from_address: \"from-address@example.com\"\ngf_smtp_from_name: \"from-name\"\ngf_smtp_skip_verify: \"true\"\n"
  },
  {
    "path": "src/app_charts/token-vendor/BUILD.bazel",
    "content": "load(\"//bazel:app.bzl\", \"app\")\nload(\"//bazel:app_chart.bzl\", \"app_chart\")\n\napp_chart(\n    name = \"token-vendor-cloud\",\n    files = [\n        \"dashboard.json\",\n    ],\n    images = {\n        \"token-vendor-go\": \"//src/go/cmd/token-vendor:token-vendor-image\",\n    },\n)\n\napp(\n    name = \"token-vendor\",\n    charts = [\n        \":token-vendor-cloud\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/dashboard.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: tokenvendor-dashboards-json\n  labels:\n    grafana: \"1\"\ndata:\n  relay.json: |-\n{{ .Files.Get \"files/dashboard.json\" | indent 4 }}\n"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: public-key-access\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.app-token-vendor.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=true\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: \"/apis/core.token-vendor/v1/public-key.read\"\n        pathType: Prefix\n        backend:\n          service:\n            name: token-vendor\n            port:\n              name: token-vendor\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: public-key-manager\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: \"http://token-vendor.app-token-vendor.svc.cluster.local/apis/core.token-vendor/v1/token.verify?robots=false\"\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: \"/apis/core.token-vendor/v1/public-key.configure\"\n        pathType: Prefix\n        backend:\n          service:\n            name: token-vendor\n            port:\n              name: token-vendor\n      - path: \"/apis/core.token-vendor/v1/public-key.publish\"\n        pathType: Prefix\n        backend:\n          service:\n            name: token-vendor\n            port:\n              name: token-vendor\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: token-vendor\nspec:\n  ingressClassName: nginx\n  rules:\n  - host: {{ .Values.domain }}\n    http:\n      paths:\n      - path: \"/apis/core.token-vendor/v1/token.verify\"\n        pathType: Prefix\n        backend:\n          service:\n            name: token-vendor\n            port:\n              name: token-vendor\n      - path: \"/apis/core.token-vendor/v1/jwt.verify\"\n        pathType: Prefix\n        backend:\n          service:\n            name: token-vendor\n            port:\n              name: token-vendor\n      - path: \"/apis/core.token-vendor/v1/token.oauth2\"\n        pathType: Prefix\n        backend:\n          service:\n            name: token-vendor\n            port:\n              name: token-vendor\n---\n"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/service-monitor.yaml",
    "content": "apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: token-vendor\n  labels:\n    prometheus: kube-prometheus\nspec:\n  endpoints:\n  - port: token-vendor\n    relabelings:\n    - sourceLabels: [__meta_kubernetes_pod_node_name]\n      targetLabel: instance\n  selector:\n    matchLabels:\n      app: token-vendor\n"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: token-vendor\n  labels:\n    # This is used by the ServiceMonitor.\n    app: token-vendor\nspec:\n  ports:\n  - port: 80\n    targetPort: 9090\n    protocol: TCP\n    name: token-vendor\n  selector:\n    app: token-vendor\n  type: ClusterIP"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/token-vendor-policy.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: token-vendor\n  annotations:\n    iam.gke.io/gcp-service-account: \"token-vendor@{{ .Values.project }}.iam.gserviceaccount.com\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: token-vendor-key-mngmt\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"*\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: token-vendor-key-mngmt-binding\nsubjects:\n- kind: ServiceAccount\n  name: token-vendor\nroleRef:\n  kind: Role\n  name: token-vendor-key-mngmt\n  apiGroup: rbac.authorization.k8s.io\n"
  },
  {
    "path": "src/app_charts/token-vendor/cloud/token-vendor.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: token-vendor\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: token-vendor\n  template:\n    metadata:\n      labels:\n        app: token-vendor\n    spec:\n      containers:\n      - name: token-vendor\n        image: {{ .Values.registry }}{{ .Values.images.token_vendor_go }}\n        args:\n        - --log-level=4 # WARN\n        - --project={{ .Values.project }}\n        - --accepted_audience=https://{{ .Values.domain }}/apis/core.token-vendor/v1/token.oauth2\n        - --service_account=robot-service\n        # This scope is for token vendor and for access to GCS/GCR.\n        - --scope=https://www.googleapis.com/auth/cloud-platform\n        # This scope allows GKE RBAC policy bindings to refer to service accounts by email.\n        # https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control#forbidden_error_for_service_accounts_on_vm_instances\n        - --scope=https://www.googleapis.com/auth/userinfo.email\n{{- if .Values.use_tv_verbose }}\n        - --verbose\n{{- end }}\n{{- if eq .Values.deploy_environment \"GCP-testing\" }}\n        - --key-store=IN_MEMORY\n{{- else }}\n        - --key-store=KUBERNETES\n        - --namespace=app-token-vendor\n{{- end }}\n        ports:\n        - name: token-vendor\n          containerPort: 9090\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 9090\n          initialDelaySeconds: 15\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n      securityContext:\n        runAsNonRoot: true\n        runAsUser: 65532\n        runAsGroup: 65532\n      tolerations:\n        - key: \"workload-identity\"\n          operator: \"Equal\"\n          value: \"true\"\n          effect: \"NoSchedule\"\n      nodeSelector:\n        iam.gke.io/gke-metadata-server-enabled: \"true\"\n      serviceAccountName: token-vendor\n"
  },
  {
    "path": "src/app_charts/token-vendor/dashboard.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"description\": \"token-vendor stats\",\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": 46,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"successful token requests\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unitScale\": true\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(tokens_requested{result=\\\"success\\\"}[$__rate_interval])\",\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Successful Requests\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"failed token requests\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unitScale\": true\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 1,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"disableTextWrap\": false,\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(tokens_requested{result=\\\"failed\\\"}[$__rate_interval])\",\n          \"fullMetaSearch\": false,\n          \"includeNullMetadata\": true,\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\",\n          \"useBackend\": false\n        }\n      ],\n      \"title\": \"Failed Requests\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"successful token verifications\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unitScale\": true\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 3,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(tokens_verified{result=\\\"success\\\"}[$__rate_interval])\",\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Successful Verifications\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"prometheus\"\n      },\n      \"description\": \"failed token verifications\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisBorderShow\": false,\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 0,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"insertNulls\": false,\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unitScale\": true\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 8\n      },\n      \"id\": 4,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"prometheus\"\n          },\n          \"editorMode\": \"code\",\n          \"expr\": \"rate(tokens_verified{result=\\\"failed\\\"}[$__rate_interval])\",\n          \"instant\": false,\n          \"legendFormat\": \"__auto\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Failed Verifications\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"schemaVersion\": 39,\n  \"tags\": [\n    \"cloud-robotics\"\n  ],\n  \"templating\": {\n    \"list\": []\n  },\n  \"time\": {\n    \"from\": \"now-6h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Token Vendor\",\n  \"uid\": \"ea96ec4a-2292-4c85-a880-846aaf4bc7b6\",\n  \"version\": 3,\n  \"weekStart\": \"\"\n}"
  },
  {
    "path": "src/bootstrap/cloud/BUILD.bazel",
    "content": "load(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\ngenrule(\n    name = \"setup-robot-digest\",\n    srcs = [\"//src/go/cmd/setup-robot:setup-robot-image.digest\"],\n    outs = [\"setup-robot.digest\"],\n    cmd = \"cp $(location //src/go/cmd/setup-robot:setup-robot-image.digest) $@\",\n)\n\npkg_tar(\n    name = \"bazel-bin\",\n    srcs = [\n        \":setup-robot-digest\",\n        \"//src/app_charts/base:base-cloud\",\n        \"//src/app_charts/platform-apps:platform-apps-cloud\",\n    ],\n    mode = \"644\",\n    package_dir = \"/bazel-bin\",\n    strip_prefix = \"/\",\n)\n\npkg_tar(\n    name = \"files\",\n    srcs = [\n        \"//:config.sh.tmpl\",\n        \"//src/bootstrap/cloud/terraform\",\n    ],\n    mode = \"644\",\n    strip_prefix = \"/\",\n)\n\npkg_tar(\n    name = \"scripts\",\n    srcs = [\n        \"//:deploy.sh\",\n        \"//scripts:common.sh\",\n        \"//scripts:config.sh\",\n        \"//scripts:include-config.sh\",\n        \"//scripts:set-config.sh\",\n        \"//src/bootstrap/robot:setup_robot.sh\",\n    ],\n    mode = \"755\",\n    strip_prefix = \"/\",\n)\n\npkg_tar(\n    name = \"binary_tools\",\n    srcs = [\n        \"//src/go/cmd/synk\",\n        \"//src/go/cmd/token-vendor:token-vendor-app\",\n        \"@hashicorp_terraform//:terraform\",\n        \"@kubernetes_helm//:helm\",\n    ],\n    mode = \"755\",\n    package_dir = \"/bin\",\n)\n\npkg_tar(\n    name = \"crc-binary\",\n    srcs = [\n        \"INSTALL_FROM_BINARY\",\n    ],\n    extension = \"tar.gz\",\n    mode = \"644\",\n    package_dir = \"/cloud-robotics-core\",\n    deps = [\n        \":bazel-bin\",\n        \":binary_tools\",\n        \":files\",\n        \":scripts\",\n    ],\n)\n"
  },
  {
    "path": "src/bootstrap/cloud/INSTALL_FROM_BINARY",
    "content": "# When this marker file is present, the deploy.sh script installs the binary instead of building\n# from local sources.\n"
  },
  {
    "path": "src/bootstrap/cloud/run-install.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{ # this ensures the entire script is downloaded #\n\nset -o pipefail -o errexit\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nBUCKET_URI=${BUCKET_URI:-\"https://storage.googleapis.com/cloud-robotics-releases\"}\nGCP_PROJECT_ID=\"$1\"\n\nif [[ -z \"$2\" || \"$2\" = --* ]]; then\n  TARGET=\"latest\"\n  COMMAND=\"$2\"\nelse\n  TARGET=\"$2\"\n  COMMAND=\"$3\"\nfi\n\nif [[ -z \"${GCP_PROJECT_ID}\" || ! \"${COMMAND}\" =~ ^(|--set-config|--set-oauth|--delete|--terraform)$ ]]; then\n  echo \"Usage: $0 <project id> [<version-file>|<tarball>] [<command>]\"\n  echo \"Supported commands:\"\n  echo \"  --set-config    Updates the cloud config interactively.\"\n  echo \"  --set-oauth     Enables and configures OAuth interactively.\"\n  echo \"  --delete        Deletes Cloud Robotics from the cloud project.\"\n  echo \"  --terraform     Apply only terraform changes.\"\n  echo \"  (default)       Install the specifies version.\"\n  exit 1\nfi\n\nif [[ ! \"${TARGET}\" = *.tar.gz ]]; then\n  echo \"Downloading version file ${BUCKET_URI}/${TARGET}\"\n  TARGET=$( curl --silent --show-error --fail \"${BUCKET_URI}/${TARGET}\" )\nfi\n\necho \"Downloading tarball ${BUCKET_URI}/${TARGET}\"\nTMPDIR=\"$( mktemp -d )\"\ncurl --silent --show-error --fail \"${BUCKET_URI}/${TARGET}\" | tar xz -C \"${TMPDIR}\"\ncd ${TMPDIR}/cloud-robotics-core\n\n# Pass xtrace on to subshells if set for this shell.\nBASH=bash\nif [[ $SHELLOPTS =~ xtrace ]] ; then\n  BASH=\"bash -o xtrace\"\nfi\n\nif [[ \"${COMMAND}\" = \"--set-config\" ]]; then\n  $BASH scripts/set-config.sh \"${GCP_PROJECT_ID}\"\nelif [[ \"${COMMAND}\" = \"--set-oauth\" ]]; then\n  $BASH scripts/set-config.sh \"${GCP_PROJECT_ID}\" --edit-oauth\nelif [[ \"${COMMAND}\" = \"--delete\" ]]; then\n  $BASH ./deploy.sh delete \"${GCP_PROJECT_ID}\"\nelse\n  # We tag the setup-robot files with this information to be able to check if\n  # cloud and robot-installations are in sync\n  export TARGET\n  $BASH scripts/set-config.sh \"${GCP_PROJECT_ID}\" --ensure-config\n  if [[ \"${COMMAND}\" = \"--terraform\" ]]; then\n    $BASH ./deploy.sh update_infra \"${GCP_PROJECT_ID}\"\n  else\n    $BASH ./deploy.sh create \"${GCP_PROJECT_ID}\"\n  fi\nfi\n\ncd ${DIR}\nrm -rf ${TMPDIR}\n\n} # this ensures the entire script is downloaded #\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/.gitignore",
    "content": ".terraform\nconfig.auto.tfvars\nterraform.tfvars\nbackend.tf\nterraform.tfstate.*.backup\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/BUILD.bazel",
    "content": "filegroup(\n    name = \"terraform\",\n    srcs = [\n        \"www.yaml\",\n    ] + glob(\n        include = [\"*.tf\"],\n        exclude = [\"backend.tf\"],\n    ),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/README.md",
    "content": "# Terraform\n\nThese files can be used to create, update or delete your personal development\nor shared projects.\n\nTo update your user project, use deploy.sh:\n\n```shell\n./deploy.sh update my-project\n```\n\nTo update shared projects such as the robolab project, run:\n\n```shell\n./deploy.sh update the-robolab-project\n```\n\nOur Terraform setup is special in two ways:\n 1. The deploy.sh script passes some variables from the config file in\n    the named project as input to enable you to switch projects naturally.\n    Terraform doesn't read the config directly, but rather through a\n    `src/bootstrap/cloud/terraform/terraform.tfvars` file that is managed by\n    deploy.sh.\n 2. The Terraform state is stored on GCS, not in your client. Each project has\n    its own state file in the GCS bucket. When initializing\n    (\"terraform init\"), you need to set -backend-config to set the right prefix.\n     deploy.sh also takes care of that.\n\nTo run terraform command locally, do:\n\n```shell\nbazel build @hashicorp_terraform//:terraform\nalias terraform=$(realpath bazel-out/../../../external/*hashicorp_terraform/terraform)\ncd src/bootstrap/cloud/terraform/\nterraform init\n```\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/address.tf",
    "content": "resource \"google_compute_address\" \"cloud_robotics\" {\n  name       = \"cloud-robotics\"\n  project    = data.google_project.project.project_id\n  region     = var.region\n  depends_on = [google_project_service.project-services[\"compute.googleapis.com\"]]\n}\n\nresource \"google_compute_address\" \"cloud_robotics_ar\" {\n  for_each   = var.additional_regions\n  name       = format(\"%s-%s\", each.key, \"ar-cloud-robotics\")\n  project    = data.google_project.project.project_id\n  region     = each.value.region\n  depends_on = [google_project_service.project-services[\"compute.googleapis.com\"]]\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/certificate-authority.tf",
    "content": "# Certificate related infrastructure.\n#\n# Configures the necessary Google private CA infrastructure to create in-cluster certificates.\n# Resources in this file are only created if the certificate_provider is set to \"google-cas\".\n\nresource \"google_privateca_ca_pool\" \"ca_pool\" {\n  project  = data.google_project.project.project_id\n  count    = var.certificate_provider == \"google-cas\" ? 1 : 0\n  name     = \"${data.google_project.project.project_id}-ca-pool\"\n  location = var.region\n  # Enterprise is recommended for long-lasting certificates\n  tier = \"ENTERPRISE\"\n\n  publishing_options {\n    publish_ca_cert = true\n    publish_crl     = true\n  }\n  issuance_policy {\n    baseline_values {\n      ca_options {\n        is_ca = false\n      }\n      key_usage {\n        base_key_usage {\n          digital_signature = true\n          key_encipherment  = true\n        }\n        extended_key_usage {\n          server_auth = true\n        }\n      }\n    }\n  }\n}\n\nresource \"google_privateca_certificate_authority\" \"ca\" {\n  project                  = data.google_project.project.project_id\n  count                    = var.certificate_provider == \"google-cas\" ? 1 : 0\n  certificate_authority_id = \"${data.google_project.project.project_id}-certificate-authority\"\n  location                 = var.region\n  pool                     = google_privateca_ca_pool.ca_pool[0].name\n  config {\n    subject_config {\n      subject {\n        organization        = var.certificate_subject_organization\n        common_name         = var.certificate_subject_common_name\n        organizational_unit = var.certificate_subject_organizational_unit\n      }\n    }\n    x509_config {\n      ca_options {\n        is_ca                  = true\n        max_issuer_path_length = 10\n      }\n      key_usage {\n        base_key_usage {\n          cert_sign         = true\n          crl_sign          = true\n          key_encipherment  = true\n          digital_signature = true\n        }\n        extended_key_usage {\n          server_auth = true\n          client_auth = true\n        }\n      }\n    }\n  }\n  type = \"SELF_SIGNED\"\n  key_spec {\n    algorithm = \"EC_P384_SHA384\"\n  }\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/cluster.tf",
    "content": "# GKE Cluster\n#\n# This creates a GKE cluster with Workload Identity enabled and a suitable\n# service account for the nodes. This service account cannot be used by the\n# workloads: see workload-identity.tf for those service accounts.\n\nresource \"google_container_cluster\" \"cloud-robotics\" {\n  project               = data.google_project.project.project_id\n  name                  = \"cloud-robotics\"\n  location              = var.cluster_type == \"zonal\" ? var.zone : var.region\n  enable_shielded_nodes = true\n  depends_on            = [google_project_service.project-services[\"container.googleapis.com\"]]\n\n  # TODO(ensonic): this is temporary for the zonal -> regional switch\n  deletion_protection = false\n\n  # Make the cluster VPC-native (default for v1.21+)\n  networking_mode = \"VPC_NATIVE\"\n\n  datapath_provider = var.datapath_provider\n  # This enables setting network policies using fully qualified domain names.\n  # See https://cloud.google.com/kubernetes-engine/docs/how-to/fqdn-network-policies#create-fqdn-network-policy\n  enable_fqdn_network_policy = var.datapath_provider == \"ADVANCED_DATAPATH\" ? true : false\n\n  # We can't create a cluster with no node pool defined, but we want to only use\n  # separately managed node pools. So we create the smallest possible default\n  # node pool and immediately delete it.\n  remove_default_node_pool = true\n\n  initial_node_count = 1\n\n  addons_config {\n    gcs_fuse_csi_driver_config {\n      enabled = true\n    }\n    gke_backup_agent_config {\n      enabled = true\n    }\n  }\n\n  gateway_api_config {\n    channel = \"CHANNEL_STANDARD\"\n  }\n\n  ip_allocation_policy {}\n\n  maintenance_policy {\n    recurring_window {\n      # Dates specifies first ocurance, times are in UTC\n      # Start late and end early to cover regions ahead/behind the sun\n      # Note: we mst not make this too small, otherwise GKE cannot schedule updates\n      start_time = \"2025-04-05T05:00:00Z\"\n      end_time   = \"2025-04-06T19:00:00Z\"\n      recurrence = \"FREQ=WEEKLY;BYDAY=SA\"\n    }\n  }\n\n  release_channel {\n    channel = \"STABLE\"\n  }\n\n  secret_manager_config {\n    enabled = var.secret_manager_plugin\n  }\n\n  timeouts {\n    create = \"1h\"\n    update = \"1h\"\n    delete = \"1h\"\n  }\n\n  vertical_pod_autoscaling {\n    enabled = true\n  }\n\n  workload_identity_config {\n    workload_pool = \"${data.google_project.project.project_id}.svc.id.goog\"\n  }\n}\n\nresource \"google_container_cluster\" \"cloud-robotics-ar\" {\n  for_each              = var.additional_regions\n  project               = data.google_project.project.project_id\n  name                  = format(\"%s-%s\", each.key, \"ar-cloud-robotics\")\n  location              = var.cluster_type == \"zonal\" ? each.value.zone : each.value.region\n  enable_shielded_nodes = true\n  depends_on            = [google_project_service.project-services[\"container.googleapis.com\"]]\n\n  # TODO(ensonic): this is temporary for the zonal -> regional switch\n  deletion_protection = false\n\n  # Make the cluster VPC-native (default for v1.21+)\n  networking_mode = \"VPC_NATIVE\"\n  ip_allocation_policy {}\n\n  # We can't create a cluster with no node pool defined, but we want to only use\n  # separately managed node pools. So we create the smallest possible default\n  # node pool and immediately delete it.\n  remove_default_node_pool = true\n\n  initial_node_count = 1\n\n  timeouts {\n    create = \"1h\"\n    update = \"1h\"\n    delete = \"1h\"\n  }\n\n  vertical_pod_autoscaling {\n    enabled = true\n  }\n\n  workload_identity_config {\n    workload_pool = \"${data.google_project.project.project_id}.svc.id.goog\"\n  }\n}\n\n# This node pool uses Workload Identity and a non-default service account, as\n# recommended for improved security.\nresource \"google_container_node_pool\" \"cloud_robotics_base_pool\" {\n  project  = data.google_project.project.project_id\n  name     = \"base-pool\"\n  location = var.cluster_type == \"zonal\" ? var.zone : var.region\n  cluster  = google_container_cluster.cloud-robotics.name\n\n  initial_node_count = 2\n\n  autoscaling {\n    min_node_count = 1\n    max_node_count = 16\n  }\n\n  node_config {\n    machine_type = \"e2-standard-4\"\n    # The GKE Metadata Server enables Workload Identity.\n    workload_metadata_config {\n      mode = \"GKE_METADATA\"\n    }\n    service_account = google_service_account.gke_node.email\n    oauth_scopes = [\n      \"https://www.googleapis.com/auth/cloud-platform\",\n      \"https://www.googleapis.com/auth/userinfo.email\",\n    ]\n  }\n}\n\nresource \"google_container_node_pool\" \"cloud_robotics_base_pool_ar\" {\n  for_each = var.additional_regions\n  project  = data.google_project.project.project_id\n  name     = format(\"%s-%s\", \"base-pool-ar\", each.key)\n  location = var.cluster_type == \"zonal\" ? each.value.zone : each.value.region\n  cluster  = google_container_cluster.cloud-robotics-ar[each.key].name\n\n  initial_node_count = 2\n\n  autoscaling {\n    min_node_count = 1\n    max_node_count = 10\n  }\n\n  node_config {\n    machine_type = \"e2-standard-4\"\n    # The GKE Metadata Server enables Workload Identity.\n    workload_metadata_config {\n      mode = \"GKE_METADATA\"\n    }\n    service_account = google_service_account.gke_node.email\n    oauth_scopes = [\n      \"https://www.googleapis.com/auth/cloud-platform\",\n      \"https://www.googleapis.com/auth/userinfo.email\",\n    ]\n  }\n}\n\n# These bindings are based on:\n# https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#use_least_privilege_sa\nresource \"google_service_account\" \"gke_node\" {\n  account_id   = \"gke-node\"\n  display_name = \"gke-node\"\n}\n\nresource \"google_project_iam_member\" \"gke_node_roles\" {\n  project = data.google_project.project.project_id\n  member  = google_service_account.gke_node.member\n  for_each = toset([\n    # GKE recommendations\n    \"roles/logging.logWriter\",\n    \"roles/monitoring.metricWriter\",\n    \"roles/stackdriver.resourceMetadata.writer\",\n  ])\n  role = each.key\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/dns.tf",
    "content": "resource \"google_dns_managed_zone\" \"external-dns\" {\n  name     = \"external-dns\"\n  dns_name = \"${var.domain}.\"\n  count    = var.domain == \"\" ? 0 : 1\n  # This is used to be true but is no longer, but we keep it here so that\n  # Terraform doesn't delete and recreate the zone.\n  description = \"Automatically managed zone by kubernetes.io/external-dns\"\n  depends_on  = [google_project_service.project-services[\"dns.googleapis.com\"]]\n}\n\nresource \"google_dns_record_set\" \"www-entry\" {\n  name  = \"${var.domain}.\"\n  count = var.domain == \"\" ? 0 : 1\n  type  = \"A\"\n\n  ttl = 300\n\n  rrdatas      = [google_compute_address.cloud_robotics.address]\n  managed_zone = google_dns_managed_zone.external-dns[0].name\n  project      = google_dns_managed_zone.external-dns[0].project\n}\n\n\n# This is a record, for example, for europe-west1.subdomain.example.com.\n# It's used to serve requests in a region closer to the user.\nresource \"google_dns_record_set\" \"www-entry-ar\" {\n  for_each = var.domain == \"\" ? {} : var.additional_regions\n\n  name = \"${each.key}.${var.domain}.\"\n  type = \"A\"\n\n  ttl = 300\n\n  rrdatas      = [google_compute_address.cloud_robotics_ar[each.key].address]\n  managed_zone = google_dns_managed_zone.external-dns[0].name\n  project      = google_dns_managed_zone.external-dns[0].project\n}\n\n# Allow cert-manager to solve DNS01 challenges in this zone.\ndata \"google_iam_policy\" \"external-dns\" {\n  binding {\n    role = \"roles/dns.admin\"\n    members = [\n      google_service_account.cert_manager.member\n    ]\n  }\n}\n\nresource \"google_dns_managed_zone_iam_policy\" \"external-dns\" {\n  count        = var.domain == \"\" ? 0 : 1\n  project      = google_dns_managed_zone.external-dns[0].project\n  managed_zone = google_dns_managed_zone.external-dns[0].name\n  policy_data  = data.google_iam_policy.external-dns.policy_data\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/endpoints.tf",
    "content": "# Do not use count to create these 2 conditionally. A deleted service needs to be manually undeleted\n# within 30 days of deletion if one wishes to use it again.\nresource \"google_endpoints_service\" \"www_service\" {\n  service_name = \"www.endpoints.${var.id}.cloud.goog\"\n  project      = var.id\n  openapi_config = templatefile(\n    \"www.yaml\",\n    {\n      GCP_PROJECT_ID = var.id\n      INGRESS_IP     = google_compute_address.cloud_robotics.address\n    }\n  )\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/gcs.tf",
    "content": "resource \"google_storage_bucket\" \"robot\" {\n  name                        = \"${var.id}-robot\"\n  location                    = var.region\n  storage_class               = \"REGIONAL\"\n  uniform_bucket_level_access = \"true\"\n  force_destroy               = \"true\"\n  depends_on                  = [google_project_service.project-services[\"storage-component.googleapis.com\"]]\n  count                       = var.onprem_federation ? 1 : 0\n}\n\nresource \"google_storage_bucket_object\" \"setup_robot_image_reference\" {\n  name          = \"setup_robot_image_reference.txt\"\n  content       = var.robot_image_reference\n  bucket        = google_storage_bucket.robot[0].name\n  cache_control = \"private, max-age=0, no-transform\"\n  count         = var.onprem_federation ? 1 : 0\n}\n\nresource \"google_storage_bucket_object\" \"setup_robot_crc_version\" {\n  name          = \"setup_robot_crc_version.txt\"\n  content       = var.crc_version\n  bucket        = google_storage_bucket.robot[0].name\n  cache_control = \"private, max-age=0, no-transform\"\n  count         = var.onprem_federation ? 1 : 0\n}\n\nresource \"google_storage_bucket_object\" \"setup_robot\" {\n  name          = \"setup_robot.sh\"\n  source        = \"../../robot/setup_robot.sh\"\n  bucket        = google_storage_bucket.robot[0].name\n  cache_control = \"private, max-age=0, no-transform\"\n  count         = var.onprem_federation ? 1 : 0\n}\n\n# We did not always create the bucket here, but sometimes elsewhere. Import it\n# to consitently manage it from here now. \nimport {\n   to = google_storage_bucket.config_store\n   id = \"${var.id}-cloud-robotics-config\"\n}\n\nresource \"google_storage_bucket\" \"config_store\" {\n  name                        = \"${var.id}-cloud-robotics-config\"\n  location                    = \"US\"\n  storage_class               = \"STANDARD\"\n  force_destroy               = true\n  uniform_bucket_level_access = true\n}\n\nresource \"google_storage_bucket_object\" \"config_store_crc_version\" {\n  name          = \"crc_version.txt\"\n  content       = var.crc_version\n  bucket        = google_storage_bucket.config_store.name\n  cache_control = \"private, max-age=0, no-transform\"\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/input.tf",
    "content": "variable \"name\" {\n  description = \"Deployment's human-readable name (my-project)\"\n}\n\nvariable \"id\" {\n  description = \"Deployment's project id (my-project)\"\n}\n\nvariable \"domain\" {\n  description = \"Deployment domain (www.example.com)\"\n}\n\nvariable \"zone\" {\n  description = \"Cloud zone to deploy to (europe-west1-c)\"\n}\n\nvariable \"region\" {\n  description = \"Cloud region to deploy to (europe-west1)\"\n}\n\nvariable \"additional_regions\" {\n  description = \"Cloud regions to deploy additional relays to\"\n  type        = map(any)\n  default     = {}\n}\n\nvariable \"shared_owner_group\" {\n  description = \"Name of a group to be added as a owner. Leave empty to not use group sharing.\"\n  default     = \"\"\n}\n\nvariable \"robot_image_reference\" {\n  description = \"Reference to the Docker image installed by the setup-robot script\"\n}\n\nvariable \"crc_version\" {\n  description = \"cloudrobotics-core version tag stored with the setup-robot script to align cloud and robot versions.\"\n}\n\nvariable \"private_image_repositories\" {\n  description = \"Projects with private GCR image repositories where we need to add IAM access rules.\"\n  type        = list(any)\n  default     = []\n}\n\nvariable \"certificate_provider\" {\n  description = \"Certificate provider to use to generate certificates for in-cluster services. Should be one of: lets-encrypt, google-cas.\"\n  type        = string\n}\n\nvariable \"certificate_subject_common_name\" {\n  description = \"Certificate Common Name (CN) field\"\n  type        = string\n}\n\nvariable \"certificate_subject_organization\" {\n  description = \"Certificate Subject Organization (O) field\"\n  type        = string\n}\n\nvariable \"certificate_subject_organizational_unit\" {\n  description = \"Certificate Subject Organizational Unit (OU) field\"\n  type        = string\n  default     = null\n}\n\nvariable \"cluster_type\" {\n  description = \"GKE cluster type. Must be one of {zonal,regional}.\"\n  type        = string\n  default     = \"zonal\"\n\n  validation {\n    condition     = contains([\"zonal\", \"regional\"], var.cluster_type)\n    error_message = \"Must be either \\\"zonal\\\" or \\\"regional\\\".\"\n  }\n}\n\nvariable \"datapath_provider\" {\n  description = \"Whether to use Dataplane v1 or v2 (DATAPATH_PROVIDER_UNSPECIFIED or ADVANCED_DATAPATH).\"\n  type        = string\n  default     = \"DATAPATH_PROVIDER_UNSPECIFIED\"\n  validation {\n    condition = contains([\"DATAPATH_PROVIDER_UNSPECIFIED\", \"ADVANCED_DATAPATH\"], var.datapath_provider)\n    error_message = \"Must be either \\\"DATAPATH_PROVIDER_UNSPECIFIED\\\" or \\\"ADVANCED_DATAPATH\\\".\"\n  }\n}\n\nvariable \"onprem_federation\" {\n  description = \"Enable google cloud robotics layer 1\"\n  type        = bool\n  default     = true\n}\n\nvariable \"secret_manager_plugin\" {\n  description = \"Enable GKE secret manager integration with GKE\"\n  type        = bool\n  default     = false\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/logging.tf",
    "content": "# New log bucket with short TTL for access logs\nresource \"google_logging_project_bucket_config\" \"remote_access_bucket\" {\n  project        = data.google_project.project.project_id\n  location       = \"global\"\n  retention_days = 2\n  bucket_id      = \"RemoteAccess\"\n}\n\n# Log all access logs to this dedicated bucket\nresource \"google_logging_project_sink\" \"remote_access_sink\" {\n  name                   = \"remote-access-sink\"\n  destination            = \"logging.googleapis.com/projects/${var.id}/locations/global/buckets/RemoteAccess\"\n  filter                 = \"resource.type=\\\"k8s_container\\\" resource.labels.cluster_name=\\\"cloud-robotics\\\" (resource.labels.container_name=\\\"nginx-ingress-controller\\\" OR resource.labels.container_name=\\\"oauth2-proxy\\\")\"\n  unique_writer_identity = true\n}\n\n# Don't store access logs in \"_Default\" bucket anymore\nresource \"google_logging_project_exclusion\" \"remote_access_exclusion\" {\n  name   = \"remote-access-exclusion\"\n  filter = \"resource.type=\\\"k8s_container\\\" resource.labels.cluster_name=\\\"cloud-robotics\\\" (resource.labels.container_name=\\\"nginx-ingress-controller\\\" OR resource.labels.container_name=\\\"oauth2-proxy\\\")\"\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/multi-cluster-ingress.tf",
    "content": "# Multi-Cluster Ingress\n#\n# If the config calls for additional regions, this registers them all into one\n# \"fleet\" (aka GKE Hub). It enables multi-cluster services and multi-cluster\n# ingress, so that the primary \"cloud-robotics\" cluster is the source-of-truth\n# for gateway configuration.\n\nresource \"google_gke_hub_feature\" \"multi_cluster_service_discovery\" {\n  count = length(var.additional_regions) > 0 ? 1 : 0\n\n  name       = \"multiclusterservicediscovery\"\n  location   = \"global\"\n  project    = data.google_project.project.project_id\n  depends_on = [google_project_service.project-services[\"gkehub.googleapis.com\"]]\n}\n\nresource \"google_gke_hub_feature\" \"multi_cluster_ingress\" {\n  count = length(var.additional_regions) > 0 ? 1 : 0\n\n  name     = \"multiclusteringress\"\n  location = \"global\"\n  project  = data.google_project.project.project_id\n  spec {\n    multiclusteringress {\n      config_membership = google_gke_hub_membership.cloud_robotics[0].id\n    }\n  }\n  depends_on = [google_project_service.project-services[\"gkehub.googleapis.com\"]]\n}\n\n# The GKE cluster called \"cloud-robotics\" is the primary cluster and the source\n# for Gateway configs.\n#\n# Both of these memberships set location = <cluster region>. I don't know if\n# this is important or if it's just Anthos metadata, but this is what\n# `gcloud container fleet memberships register` does (as opposed to what its\n# docs say, which is that it uses the location rather than the region of the\n# cluster, which could be a zone).\nresource \"google_gke_hub_membership\" \"cloud_robotics\" {\n  count = length(var.additional_regions) > 0 ? 1 : 0\n\n  membership_id = \"cloud-robotics\"\n  project       = data.google_project.project.project_id\n  location      = var.region\n  endpoint {\n    gke_cluster {\n      resource_link = google_container_cluster.cloud-robotics.id\n    }\n  }\n  depends_on = [google_project_service.project-services[\"gkehub.googleapis.com\"]]\n}\n\nresource \"google_gke_hub_membership\" \"cloud_robotics_ar\" {\n  for_each      = var.additional_regions\n  project       = data.google_project.project.project_id\n  membership_id = format(\"%s-%s\", each.key, \"ar-cloud-robotics\")\n  location      = each.value.region\n  endpoint {\n    gke_cluster {\n      resource_link = google_container_cluster.cloud-robotics-ar[each.key].id\n    }\n  }\n  depends_on = [google_project_service.project-services[\"gkehub.googleapis.com\"]]\n}\n\n# The following IAM policies are required by the Gateway API controllers.\nresource \"google_project_iam_member\" \"multi_cluster_service_importer_network_viewer\" {\n  count = length(var.additional_regions) > 0 ? 1 : 0\n\n  project    = data.google_project.project.project_id\n  role       = \"roles/compute.networkViewer\"\n  member     = \"serviceAccount:${data.google_project.project.project_id}.svc.id.goog[gke-mcs/gke-mcs-importer]\"\n  depends_on = [google_container_cluster.cloud-robotics]\n}\n\nresource \"google_project_iam_member\" \"multi_cluster_ingress_controller_container_admin\" {\n  count = length(var.additional_regions) > 0 ? 1 : 0\n\n  project    = data.google_project.project.project_id\n  role       = \"roles/container.admin\"\n  member     = \"serviceAccount:service-${data.google_project.project.number}@gcp-sa-multiclusteringress.iam.gserviceaccount.com\"\n  depends_on = [google_project_service.project-services[\"multiclusteringress.googleapis.com\"]]\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/output.tf",
    "content": "output \"ingress-ip\" {\n  value = google_compute_address.cloud_robotics.address\n}\n\noutput \"ingress-ip-ar\" {\n  value = {\n    for address in google_compute_address.cloud_robotics_ar : address.name => address.address\n  }\n}\n\noutput \"cluster-location\" {\n  value = google_container_cluster.cloud-robotics.location\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/project.tf",
    "content": "# We've stopped managing Google Cloud projects in this Terraform, make sure they\n# aren't deleted. \nremoved {\n  from = google_project.project\n  lifecycle {\n    destroy = false\n  }\n}\n\ndata \"google_project\" \"project\" {\n  project_id = var.id\n}\n\nresource \"google_project_iam_member\" \"owner_group\" {\n  count   = var.shared_owner_group == \"\" ? 0 : 1\n  project = data.google_project.project.project_id\n  role    = \"roles/editor\"\n  member  = \"group:${var.shared_owner_group}\"\n}\n\n# We can't use google_project_services because Endpoints adds services\n# dynamically.\nresource \"google_project_service\" \"project-services\" {\n  project            = data.google_project.project.project_id\n  disable_on_destroy = false\n  for_each = toset(concat([\n    \"artifactregistry.googleapis.com\",\n    # Next 2 are needed for Terraform's data.google_project.project.resource to work.\n    \"cloudbilling.googleapis.com\",\n    \"cloudresourcemanager.googleapis.com\",\n    \"compute.googleapis.com\",\n    \"container.googleapis.com\",\n    \"containersecurity.googleapis.com\",\n    \"dns.googleapis.com\",\n    \"endpoints.googleapis.com\",\n    \"iam.googleapis.com\",\n    \"iamcredentials.googleapis.com\",\n    \"logging.googleapis.com\",\n    \"servicecontrol.googleapis.com\",\n    # Next 2 are needed fro terraform again\n    \"servicemanagement.googleapis.com\",\n    \"serviceusage.googleapis.com\",\n    \"storage-component.googleapis.com\",\n    ], length(var.additional_regions) == 0 ? [] : [\n    # Following APIs are only needed when using multi-cluster gateways.\n    \"gkeconnect.googleapis.com\",\n    \"gkehub.googleapis.com\",\n    \"trafficdirector.googleapis.com\",\n    \"multiclusterservicediscovery.googleapis.com\",\n    \"multiclusteringress.googleapis.com\",\n  ]))\n  service = each.value\n}\n\n# This is needed to allow creating certificates in GCP.\nresource \"google_project_service\" \"certificateauthority\" {\n  project = data.google_project.project.project_id\n  # Only enable if Google CAS is the Certificate Authority\n  count              = var.certificate_provider == \"google-cas\" ? 1 : 0\n  service            = \"privateca.googleapis.com\"\n  disable_on_destroy = false\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/provider.tf",
    "content": "provider \"google\" {\n  project = var.id\n  region  = var.region\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/registry.tf",
    "content": "# Container registry configuration\n\nlocals {\n  service_acounts = flatten([\n    google_service_account.gke_node.member,\n    google_service_account.human-acl.member,\n    var.onprem_federation ? [google_service_account.robot-service[0].member] : [],\n  ])\n  # TODO: use the regional repos depending on settings in the future\n  private_repo_access = flatten([\n    for sa in local.service_acounts : [\n      for prj in var.private_image_repositories : {\n        prj = prj\n        sa  = sa\n      }\n    ]\n  ])\n  std_repositories = [\n    { repository = \"asia.gcr.io\", location=\"asia\" },\n    { repository = \"eu.gcr.io\", location=\"europe\" },\n    { repository = \"gcr.io\", location=\"us\" },\n    { repository = \"us.gcr.io\", location=\"us\" },\n  ]\n}\n\n# import existing repos, see: gcloud artifacts repositories list --project=<project-id>\n# uncomment if updating an old project, we can't leave it on by default due to:\n# https://github.com/hashicorp/terraform/issues/33633\n#import {\n#  id = \"projects/${data.google_project.project.project_id}/locations/asia/repositories/asia.gcr.io\"\n#  to = google_artifact_registry_repository.gcrio_repositories[0]\n#}\n#import {\n#  id = \"projects/${data.google_project.project.project_id}/locations/europe/repositories/eu.gcr.io\"\n#  to = google_artifact_registry_repository.gcrio_repositories[1]\n#}\n#import {\n#  id = \"projects/${data.google_project.project.project_id}/locations/us/repositories/gcr.io\"\n#  to = google_artifact_registry_repository.gcrio_repositories[2]\n#}\n#import {\n#  id = \"projects/${data.google_project.project.project_id}/locations/us/repositories/us.gcr.io\"\n#  to = google_artifact_registry_repository.gcrio_repositories[3]\n#}\n\nresource \"google_artifact_registry_repository\" \"gcrio_repositories\" {\n  project       = data.google_project.project.project_id\n  location      = local.std_repositories[count.index].location\n  repository_id = local.std_repositories[count.index].repository\n  format        = \"docker\"\n\n  cleanup_policy_dry_run = false\n  cleanup_policies {\n    id     = \"delete-untagged\"\n    action = \"DELETE\"\n    condition {\n      tag_state    = \"UNTAGGED\"\n      older_than   = \"30d\"\n    }\n  }\n\n  count         = length(local.std_repositories)\n}\n\nresource \"google_artifact_registry_repository_iam_member\" \"gcrio_gar_reader\" {\n  project    = data.google_project.project.project_id\n  location   = \"us\"\n  repository = \"gcr.io\"\n  role       = \"roles/artifactregistry.reader\"\n  count      = length(local.service_acounts)\n  member     = local.service_acounts[count.index]\n  depends_on = [ google_artifact_registry_repository.gcrio_repositories ]\n}\n\nresource \"google_artifact_registry_repository_iam_member\" \"private_gcrio_gar_reader\" {\n  location   = \"us\"\n  repository = \"gcr.io\"\n  role       = \"roles/artifactregistry.reader\"\n  count      = length(local.private_repo_access)\n  project    = local.private_repo_access[count.index].prj\n  member     = local.private_repo_access[count.index].sa\n  depends_on = [ google_artifact_registry_repository.gcrio_repositories ]\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/service-account.tf",
    "content": "# Configuration for the following service accounts:\n#\n# - robot-service@, which is used by workloads on robot clusters to access GCP\n#   APIs, as well as services like the k8s-relay though ingress-nginx.\n# - human-acl@, which is used as a \"virtual permission\" for users of the\n#   cluster, allowing access to services like Grafana through ingress-nginx.\n#   It can also be used to generate tokens for registering new clusters to the\n#   cloud.\n\nresource \"google_service_account\" \"robot-service\" {\n  account_id   = \"robot-service\"\n  display_name = \"robot-service\"\n  project      = data.google_project.project.project_id\n  count        = var.onprem_federation ? 1 : 0\n}\n\n# Allow the the token-vendor to impersonate the \"robot-service\" service account\n# and to create new tokens for it.\ndata \"google_iam_policy\" \"robot-service\" {\n  binding {\n    # Security note from b/120897889: This permission allows privilege escalation\n    # if granted too widely. Make sure that the robot-service can't mint tokens\n    # for accounts other than itself! If in doubt, review this section carefully.\n    # In particular, this serves as the default service account for all containers\n    # running in the GKE cluster\n    role = \"roles/iam.serviceAccountTokenCreator\"\n\n    members = [\n      google_service_account.token_vendor.member,\n    ]\n  }\n\n  binding {\n    role = \"roles/iam.serviceAccountUser\"\n\n    members = [\n      google_service_account.token_vendor.member,\n\n      # This seemingly nonsensical binding is necessary for the robot auth\n      # path in the K8s relay, which has to work with GCP auth tokens.\n      google_service_account.robot-service[0].member,\n    ]\n  }\n\n  count = var.onprem_federation ? 1 : 0\n}\n\n# Bind policy to the \"robot-service\" service account.\n# More: https://cloud.google.com/iam/docs/service-accounts#service_account_permissions\nresource \"google_service_account_iam_policy\" \"robot-service\" {\n  service_account_id = google_service_account.robot-service[0].name\n  policy_data        = data.google_iam_policy.robot-service[0].policy_data\n  count              = var.onprem_federation ? 1 : 0\n}\n\nresource \"google_project_iam_member\" \"robot-service-roles\" {\n  project = data.google_project.project.project_id\n  member  = google_service_account.robot-service[0].member\n  for_each = var.onprem_federation ? toset([\n    \"roles/cloudtrace.agent\",        # Upload cloud traces\n    \"roles/container.clusterViewer\", # Sync CRs from the GKE cluster.\n    \"roles/logging.logWriter\",       # Upload text logs to Cloud logging\n    # Required to use robot-service@ for GKE clusters that simulate robots\n    \"roles/monitoring.viewer\",\n  ]) : toset([])\n  role = each.value\n}\n\nresource \"google_service_account\" \"human-acl\" {\n  account_id   = \"human-acl\"\n  display_name = \"human-acl\"\n  project      = data.google_project.project.project_id\n}\n\nresource \"google_service_account_iam_member\" \"human-acl-shared-owner-account-user\" {\n  count              = var.shared_owner_group == \"\" ? 0 : 1\n  service_account_id = google_service_account.human-acl.name\n  role               = \"roles/iam.serviceAccountUser\"\n  member             = \"group:${var.shared_owner_group}\"\n}\n\n###\n# The following permissions make human-acl@ tokens work with setup_robot.sh.\n# To create such tokens, the user needs roles/iam.serviceAccountTokenCreator.\n# https://cloud.google.com/iam/docs/create-short-lived-credentials-direct\n#\n# This also RBAC policy to create Robot CRs, defined in\n# src/app_charts/base/cloud/registry-policy.yaml.\n###\n\n# Allow reading GCS objects such as setup_robot_crc_version.txt.\nresource \"google_project_iam_member\" \"human-acl-object-viewer\" {\n  project = data.google_project.project.project_id\n  role    = \"roles/storage.objectViewer\"\n  member  = google_service_account.human-acl.member\n}\n\n# Allow robot registration with the token vendor, which checks if the client's\n# token can \"act as\" the human-acl@ SA. We need this binding even if the\n# client provided a token for the human-acl@ SA itself.\nresource \"google_service_account_iam_member\" \"human-acl-act-as-self\" {\n  service_account_id = google_service_account.human-acl.name\n  role               = \"roles/iam.serviceAccountUser\"\n  member             = google_service_account.human-acl.member\n}\n\n# Grant permissions to generate tokens for registering new workcell clusters.\n# This lets users run:\n#   gcloud auth print-access-token \\\n#     --impersonate-service-account=human-acl@${PROJECT_ID}.iam.gserviceaccount.com\n# so they can register new workcell clusters without passing their own tokens\n# (which aren't limited to a single GCP project) into the cluster.\nresource \"google_service_account_iam_member\" \"human-acl-token-generator\" {\n  service_account_id = google_service_account.human-acl.name\n  role               = \"roles/iam.serviceAccountTokenCreator\"\n  member             = \"group:${var.shared_owner_group}\"\n  count              = var.shared_owner_group == \"\" ? 0 : 1\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/versions.tf",
    "content": "terraform {\n  required_providers {\n    google = {\n      source = \"hashicorp/google\"\n    }\n  }\n  required_version = \">= 1.11.4\"\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/workload-identity.tf",
    "content": "# IAM Configuration for GKE cluster workloads using Workload Identity.\n# See also cluster.tf.\n\n# token-vendor\n##############\nresource \"google_service_account\" \"token_vendor\" {\n  account_id   = \"token-vendor\"\n  display_name = \"token-vendor\"\n  project      = data.google_project.project.project_id\n}\n\n# Allow the app-token-vendor/token-vendor Kubernetes SA to use this GCP SA.\nresource \"google_service_account_iam_policy\" \"token_vendor\" {\n  service_account_id = google_service_account.token_vendor.name\n  policy_data        = data.google_iam_policy.token_vendor.policy_data\n  # Avoid Error 400: Identity Pool does not exist (my-project.svc.id.goog).\n  depends_on = [google_container_cluster.cloud-robotics]\n}\n\ndata \"google_iam_policy\" \"token_vendor\" {\n  binding {\n    role = \"roles/iam.workloadIdentityUser\"\n    members = [\n      \"serviceAccount:${data.google_project.project.project_id}.svc.id.goog[app-token-vendor/token-vendor]\"\n    ]\n  }\n}\n\n# Note: the policy in service-account.tf allows the token-vendor to create\n# new tokens for the robot-service@ service account.\n\n# cert-manager\n##############\n\nresource \"google_service_account\" \"cert_manager\" {\n  account_id   = \"cert-manager\"\n  display_name = \"cert-manager\"\n  project      = data.google_project.project.project_id\n}\n\ndata \"google_iam_policy\" \"cert_manager\" {\n  binding {\n    role = \"roles/iam.workloadIdentityUser\"\n    members = [\n      \"serviceAccount:${data.google_project.project.project_id}.svc.id.goog[default/cert-manager]\",\n    ]\n  }\n}\n\nresource \"google_service_account_iam_policy\" \"cert_manager\" {\n  service_account_id = google_service_account.cert_manager.id\n  policy_data        = data.google_iam_policy.cert_manager.policy_data\n  depends_on         = [google_container_cluster.cloud-robotics]\n}\n\n# Instead of granting dns.admin on the whole project as recommended, grant\n# reader on the project (needed to list managed zones) and admin on the\n# individual zones that cert-manager uses.\nresource \"google_project_iam_member\" \"cert_manager_dns_reader\" {\n  project = data.google_project.project.project_id\n  role    = \"roles/dns.reader\"\n  member  = google_service_account.cert_manager.member\n}\n\n# cert-manager-google-cas-issuer\n################################\n\n###\n# The following resources enable Google's Certificate Authority Service (CAS) support.\n# They are required to access the CAS service to generate certificates for cluster resources.\n###\n\nresource \"google_service_account\" \"google-cas-issuer\" {\n  count        = var.certificate_provider == \"google-cas\" ? 1 : 0\n  account_id   = \"sa-google-cas-issuer\"\n  display_name = \"sa-google-cas-issuer\"\n  description  = \"Service account used by GKE cert-manager's google-cas-issuer to emit certificates using Google's Certificate Authority Service (CAS).\"\n}\n\n# Bind IAM policies to the \"sa-google-cas-issuer\" service account.\n\n# Allow the SA to create private CA pool certificates.\nresource \"google_privateca_ca_pool_iam_member\" \"ca-pool-workload-identity\" {\n  count = var.certificate_provider == \"google-cas\" ? 1 : 0\n\n  ca_pool = google_privateca_ca_pool.ca_pool[0].id\n  role    = \"roles/privateca.certificateManager\"\n  member  = google_service_account.google-cas-issuer[0].member\n}\n\n# Define IAM policy for the workload identity user.\n# This allows the Kubernetes service account to act as the GKE service account.\ndata \"google_iam_policy\" \"google-cas-issuer\" {\n  count = var.certificate_provider == \"google-cas\" ? 1 : 0\n\n  binding {\n    role = \"roles/iam.workloadIdentityUser\"\n    members = [\n      \"serviceAccount:${data.google_project.project.project_id}.svc.id.goog[default/cert-manager-google-cas-issuer]\",\n    ]\n  }\n}\n\nresource \"google_service_account_iam_policy\" \"google-cas-issuer\" {\n  count              = var.certificate_provider == \"google-cas\" ? 1 : 0\n  service_account_id = google_service_account.google-cas-issuer[0].id\n  policy_data        = data.google_iam_policy.google-cas-issuer[0].policy_data\n  depends_on         = [google_container_cluster.cloud-robotics]\n}\n"
  },
  {
    "path": "src/bootstrap/cloud/terraform/www.yaml",
    "content": "swagger: \"2.0\"\ninfo:\n  version: \"1.0.0\"\n  title: Cloud Robotics APIs\n  description: Example of the bare minimum Swagger spec\nhost: www.endpoints.${GCP_PROJECT_ID}.cloud.goog\nx-google-endpoints:\n    - name: \"www.endpoints.${GCP_PROJECT_ID}.cloud.goog\"\n      target: \"${INGRESS_IP}\"\npaths:\n  /:\n    get:\n      operationId: getAll\n      responses:\n        \"200\":\n          description:  OK\n"
  },
  {
    "path": "src/bootstrap/robot/BUILD.bazel",
    "content": "exports_files([\n    \"setup_robot.sh\",\n])\n"
  },
  {
    "path": "src/bootstrap/robot/setup_robot.sh",
    "content": "#!/bin/bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script is a convenience wrapper for starting the setup-robot container, i.e., for doing\n# \"kubectl run ... --image=...setup-robot...\".\n\nset -e\nset -o pipefail\n\nfunction kc {\n  kubectl --context=\"${KUBE_CONTEXT}\" \"$@\"\n}\n\nfunction faketty {\n  # Run command inside a TTY.\n  script -qfec \"$(printf \"%q \" \"$@\")\" /dev/null\n}\n\nif [[ ! \"$*\" =~ \"--project\" && $# -ge 2 ]] ; then\n  echo \"WARNING: using only positional arguments for setup_robot.sh is deprecated.\" >&2\n  echo \"    Please use the following invocation instead. Setup continues in 60 seconds...\" >&2\n  echo \"    setup-robot <robot-name> --project <project-id> \\\\\" >&2\n  echo \"        [--robot-type <type>] [--app-management]\" >&2\n  # Sleep, as the warning can't be seen after the helm output fills the screen.\n  sleep 60\n  # Rewrite parameters to new usage.\n  set -- \"$2\" --project \"$1\" --robot-type \"${3:-}\"\nfi\n\n# Extract the project from the command-line args. It is required to identify the reference for the\n# setup-robot image. This is challenging as --project is an option, so we have to do some\n# rudimentary CLI parameter parsing.\nfor i in $(seq 1 $#) ; do\n  if [[ \"${!i}\" == \"--project\" ]] ; then\n    j=$((i+1))\n    PROJECT=${!j}\n  fi\ndone\n\nif [[ -z \"$PROJECT\" ]] ; then\n  echo \"ERROR: --project <project-id> is required\" >&2\n  exit 1\nfi\n\nif [[ -z \"${KUBE_CONTEXT}\" ]] ; then\n  KUBE_CONTEXT=kubernetes-admin@kubernetes\nfi\n\nif [[ -n \"$ACCESS_TOKEN_FILE\" ]]; then\n  ACCESS_TOKEN=$(cat ${ACCESS_TOKEN_FILE})\nfi\n\nif [[ -z \"$ACCESS_TOKEN\" ]]; then\n  echo \"Generate access token with gcloud:\"\n  echo \"    gcloud auth application-default print-access-token\"\n  echo \"Enter access token:\"\n  read ACCESS_TOKEN\nfi\n\nif [[ -z \"${HOST_HOSTNAME}\" ]] ; then\n  HOST_HOSTNAME=$(hostname)\nfi\n\n# Full reference to the setup-robot image.\nIMAGE_REFERENCE=$(curl -fsSL -H \"Authorization: Bearer ${ACCESS_TOKEN}\" \\\n\"https://storage.googleapis.com/${PROJECT}-robot/setup_robot_image_reference.txt\") || \\\n  IMAGE_REFERENCE=\"\"\n\nCRC_VERSION=$(curl -fsSL -H \"Authorization: Bearer ${ACCESS_TOKEN}\" \\\n\"https://storage.googleapis.com/${PROJECT}-robot/setup_robot_crc_version.txt\") || \\\n  CRC_VERSION=\"\"\n\nif [[ -z \"$IMAGE_REFERENCE\" ]] ; then\n  echo \"ERROR: failed to get setup_robot_image_reference.txt from GCS\" >&2\n  exit 1\nfi\n\n# Extract registry from IMAGE_REFERENCE. E.g.:\n# IMAGE_REFERENCE = \"eu.gcr.io/my-project/setup-robot@sha256:07...5465244d\"\n# REGISTRY = \"eu.gcr.io/my-project\"\n# REGISTRY_DOMAIN = \"eu.gcr.io\"\nREGISTRY=${IMAGE_REFERENCE%/*}\nREGISTRY_DOMAIN=${IMAGE_REFERENCE%%/*}\n\nif [[ \"$SKIP_LOCAL_PULL\" != \"true\" && \"$REGISTRY\" != \"gcr.io/cloud-robotics-releases\" ]] ; then\n  # The user has built setup-robot from source and pushed it to a private\n  # registry. If so, k8s may not yet have credentials that can pull from a\n  # private registry, so do it directly.\n  echo \"Pulling image from ${REGISTRY_DOMAIN}...\"\n\n  private_registry_enabled=0\n  if hash docker &> /dev/null ; then\n    echo ${ACCESS_TOKEN} | docker login -u oauth2accesstoken --password-stdin https://${REGISTRY_DOMAIN} || true\n\n    if docker pull ${IMAGE_REFERENCE}; then\n      private_registry_enabled=1\n    else\n      docker logout https://${REGISTRY_DOMAIN}\n      echo \"WARNING: failed to pull setup-robot image using 'docker pull'\" >&2\n    fi\n    docker logout https://${REGISTRY_DOMAIN}\n  fi\n  if hash crictl &> /dev/null ; then\n    if crictl pull --creds \"oauth2accesstoken:${ACCESS_TOKEN}\" \"${IMAGE_REFERENCE}\" ; then\n      private_registry_enabled=1\n    else\n      echo \"WARNING: failed to pull setup-robot image using 'crictl pull'\" >&2\n    fi\n  fi\n  if [[ $private_registry_enabled == \"0\" ]]; then\n    echo \"ERROR: failed to find 'crictl' or 'docker' binary. This is required when\" >&2\n    echo \"       Cloud Robotics Core was deployed from source.\" >&2\n    exit 1\n  fi\nfi\n\n# Wait for creation of the default service account.\n# https://github.com/kubernetes/kubernetes/issues/66689\ni=0\nuntil kc get serviceaccount default &>/dev/null; do\n  sleep 1\n  i=$((i + 1))\n  if ((i >= 60)) ; then\n    # Try again, without suppressing stderr this time.\n    if ! kc get serviceaccount default >/dev/null; then\n      echo \"ERROR: 'kubectl get serviceaccount default' failed\" >&2\n      exit 1\n    fi\n  fi\ndone\n\n# Remove old unmanaged cert\nif ! kc get secrets cluster-authority --ignore-not-found -o yaml | grep -q \"cert-manager.io/certificate-name: selfsigned-ca\"; then\n  kc delete secrets cluster-authority 2> /dev/null || true\nfi\n\n# Remove legacy helm resources\nkc -n kube-system delete deploy tiller-deploy 2> /dev/null || true\nkc -n kube-system delete service tiller-deploy 2> /dev/null || true\nkc -n kube-system delete clusterrolebinding tiller 2> /dev/null || true\nkc -n kube-system delete sa tiller 2> /dev/null || true\nkc -n kube-system delete cm -l OWNER=TILLER 2> /dev/null\n# Cleanup old resources\nkc -n default delete secret robot-master-tls 2> /dev/null || true\n\n# Remove previous instance, in case installation was canceled\nkc delete pod setup-robot 2> /dev/null || true\nfaketty kubectl --context \"${KUBE_CONTEXT}\" run setup-robot --restart=Never -it --rm \\\n  --pod-running-timeout=3m \\\n  --image=${IMAGE_REFERENCE} \\\n  --env=\"ACCESS_TOKEN=${ACCESS_TOKEN}\" \\\n  --env=\"REGISTRY=${REGISTRY}\" \\\n  --env=\"HOST_HOSTNAME=${HOST_HOSTNAME}\" \\\n  --env=\"CRC_VERSION=${CRC_VERSION}\" \\\n  -- \"$@\"\n"
  },
  {
    "path": "src/go/cmd/app-rollout-controller/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/app-rollout-controller\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/controller/approllout:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/scheme:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_helm//pkg/chartutil:go_default_library\",\n        \"@io_k8s_helm//pkg/strvals:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/healthz:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/log:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/log/zap:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/manager:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/manager/signals:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/metrics/server:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/webhook:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"app-rollout-controller-app\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n)\n\npkg_tar(\n    name = \"app-rollout-controller-image-layer\",\n    srcs = [\":app-rollout-controller-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"app-rollout-controller-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/app-rollout-controller-app\"],\n    tars = [\":app-rollout-controller-image-layer\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/app-rollout-controller/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Runs the app rollout controller which creates and deletes Kubernetes deployments\n// to bring them into agreement with configuration.\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tregistry \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/controller/approllout\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/helm/pkg/chartutil\"\n\t\"k8s.io/helm/pkg/strvals\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\tctrllog \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager/signals\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n)\n\nvar (\n\tparams      = flag.String(\"params\", \"\", \"Helm configuration parameters formatted as name=value,topname.subname=value\")\n\thealthzPort = flag.Int(\"healthz-port\", 8080, \"Listening port of the /healthz probe\")\n\twebhookPort = flag.Int(\"webhook-port\", 9876, \"Listening port of the custom resource webhook\")\n\tcertDir     = flag.String(\"cert-dir\", \"\", \"Directory for TLS certificates\")\n\tlogLevel    = flag.Int(\"log-level\", int(slog.LevelInfo), \"the log message level required to be logged\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tlogHandler := ilog.NewLogHandler(slog.Level(*logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tctx := context.Background()\n\tkubernetesConfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tslog.Error(\"Failed to initialize Kubernetes config\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\thelmParams, err := strvals.ParseString(*params)\n\tif err != nil {\n\t\tslog.Error(\"invalid Helm parameters\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\tif err := runController(ctx, kubernetesConfig, helmParams); err != nil {\n\t\tslog.Error(\"Exit\", ilog.Err(runController(ctx, kubernetesConfig, helmParams)))\n\t\tos.Exit(1)\n\t}\n\tslog.Info(\"Exit\")\n}\n\nfunc runController(ctx context.Context, cfg *rest.Config, params map[string]interface{}) error {\n\tctrllog.SetLogger(zap.New())\n\n\tsc := runtime.NewScheme()\n\tscheme.AddToScheme(sc)\n\tapps.AddToScheme(sc)\n\tregistry.AddToScheme(sc)\n\n\tmgr, err := manager.New(cfg, manager.Options{\n\t\tScheme:                 sc,\n\t\tWebhookServer:          webhook.NewServer(webhook.Options{CertDir: *certDir, Port: *webhookPort}),\n\t\tMetrics:                metricsserver.Options{BindAddress: \"0\"}, // disabled\n\t\tHealthProbeBindAddress: fmt.Sprintf(\":%d\", *healthzPort),\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"create controller manager\")\n\t}\n\tif err := approllout.Add(ctx, mgr, chartutil.Values(params)); err != nil {\n\t\treturn errors.Wrap(err, \"add AppRollout controller\")\n\t}\n\tif err := mgr.AddHealthzCheck(\"trivial\", healthz.Ping); err != nil {\n\t\treturn errors.Wrap(err, \"add healthz check\")\n\t}\n\n\tsrv := mgr.GetWebhookServer()\n\n\tsrv.Register(\"/approllout/validate\", approllout.NewAppRolloutValidationWebhook(mgr))\n\tsrv.Register(\"/app/validate\", approllout.NewAppValidationWebhook(mgr))\n\n\treturn mgr.Start(signals.SetupSignalHandler())\n}\n"
  },
  {
    "path": "src/go/cmd/chart-assignment-controller/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/chart-assignment-controller\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/controller/chartassignment:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/scheme:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/healthz:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/log:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/log/zap:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/manager:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/manager/signals:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/metrics/server:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/webhook:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n        \"@io_opencensus_go_contrib_exporter_stackdriver//:go_default_library\",\n    ],\n)\n\ngenrule(\n    name = \"helm-config\",\n    outs = [\"helm-config.tar\"],\n    # To make the rule deterministic\n    #  - Remove the \"generated\" timestamp in repositories.yaml.\n    #  - Add some extra tar flags.\n    cmd = \"HELM_HOME=$$PWD/.helm $(location @kubernetes_helm//:helm) >/dev/null init --client-only --skip-repos \" +\n          \"&& sed -i '/generated:/d' .helm/repository/repositories.yaml \" +\n          \"&& tar --owner=root --group=root --numeric-owner --mtime='2010-01-01' --create --file $@ .helm\",\n    output_to_bindir = True,\n    tools = [\"@kubernetes_helm//:helm\"],\n)\n\noci_image(\n    name = \"helm-image\",\n    base = \"@distroless_cc\",\n    tars = [\":helm-image-layer\"],\n)\n\npkg_tar(\n    name = \"helm-image-layer\",\n    extension = \"tar.gz\",\n    package_dir = \"/home/nonroot/\",\n    deps = [\":helm-config\"],\n)\n\ngo_binary(\n    name = \"chart-assignment-controller-app\",\n    embed = [\":go_default_library\"],\n)\n\npkg_tar(\n    name = \"chart-assignment-controller-layer\",\n    srcs = [\":chart-assignment-controller-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"chart-assignment-controller-image\",\n    base = \":helm-image\",\n    entrypoint = [\"/chart-assignment-controller-app\"],\n    tars = [\":chart-assignment-controller-layer\"],\n)\n"
  },
  {
    "path": "src/go/cmd/chart-assignment-controller/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package main defines the entry point for the chart assignment controller service.\n//\n// Ensures selected apps are running on the robot.\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\n\t\"contrib.go.opencensus.io/exporter/stackdriver\"\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/controller/chartassignment\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\t\"go.opencensus.io/trace\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\tctrllog \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager/signals\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n)\n\nvar (\n\tcloudCluster         = flag.Bool(\"cloud-cluster\", true, \"Is the controller deployed in cloud cluster\")\n\thealthzPort          = flag.Int(\"healthz-port\", 8080, \"Listening port of the /healthz probe\")\n\twebhookEnabled       = flag.Bool(\"webhook-enabled\", true, \"Whether the webhook should be served\")\n\twebhookPort          = flag.Int(\"webhook-port\", 9876, \"Listening port of the custom resource webhook\")\n\tcertDir              = flag.String(\"cert-dir\", \"\", \"Directory for TLS certificates\")\n\tstackdriverProjectID = flag.String(\"trace-stackdriver-project-id\", \"\", \"If not empty, traces will be uploaded to this Google Cloud Project. Not relevant for cloud cluster\")\n\tmaxQPS               = flag.Int(\"apiserver-max-qps\", 50, \"Maximum number of calls to the API server per second.\")\n\tlogLevel             = flag.Int(\"log-level\", int(slog.LevelInfo), \"the log message level required to be logged\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tlogHandler := ilog.NewLogHandler(slog.Level(*logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tctx := context.Background()\n\tif *stackdriverProjectID != \"\" && *cloudCluster == false {\n\t\tsd, err := stackdriver.NewExporter(stackdriver.Options{\n\t\t\tProjectID: *stackdriverProjectID,\n\t\t})\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to create the Stackdriver exporter\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\ttrace.RegisterExporter(sd)\n\t\ttrace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})\n\t\tdefer sd.Flush()\n\t}\n\n\tvar clusterName string\n\tif *cloudCluster == true {\n\t\tclusterName = \"cloud\"\n\t\tslog.Info(\"Starting chart-assignment-controller in cloud setup\")\n\t} else {\n\t\tclusterName = os.Getenv(\"ROBOT_NAME\")\n\t\tslog.Info(\"Starting chart-assigment-controller in robot setup\", slog.String(\"Cluster\", clusterName))\n\t\tif clusterName == \"\" {\n\t\t\tslog.Error(\"expect ROBOT_NAME environment var to be set to an non-empty string\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tconfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tslog.Error(\"Failed to load config\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tconfig.QPS = float32(*maxQPS)\n\t// The default value of twice the max QPS seems to work well.\n\tconfig.Burst = *maxQPS * 2\n\n\tif err := runController(ctx, config, clusterName); err != nil {\n\t\tslog.Error(\"Controller terminated\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tslog.Info(\"Controller finished\")\n}\n\nfunc runController(ctx context.Context, cfg *rest.Config, cluster string) error {\n\tctrllog.SetLogger(zap.New())\n\n\tsc := runtime.NewScheme()\n\tscheme.AddToScheme(sc)\n\tapps.AddToScheme(sc)\n\n\tmgr, err := manager.New(cfg, manager.Options{\n\t\tScheme:                 sc,\n\t\tWebhookServer:          webhook.NewServer(webhook.Options{CertDir: *certDir, Port: *webhookPort}),\n\t\tMetrics:                metricsserver.Options{BindAddress: \"0\"}, // disabled\n\t\tHealthProbeBindAddress: fmt.Sprintf(\":%d\", *healthzPort),\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"create controller manager\")\n\t}\n\tif err := chartassignment.Add(ctx, mgr, *cloudCluster); err != nil {\n\t\treturn errors.Wrap(err, \"add ChartAssignment controller\")\n\t}\n\tif err := mgr.AddHealthzCheck(\"trivial\", healthz.Ping); err != nil {\n\t\treturn errors.Wrap(err, \"add healthz check\")\n\t}\n\n\tif *webhookEnabled {\n\t\twebhook := chartassignment.NewValidationWebhook(mgr)\n\t\tsrv := mgr.GetWebhookServer()\n\t\tsrv.Register(\"/chartassignment/validate\", webhook)\n\t}\n\n\treturn mgr.Start(signals.SetupSignalHandler())\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\", \"go_test\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"health.go\",\n        \"main.go\",\n        \"syncer.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/cr-syncer\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_motemen_go_loghttp//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:go_default_library\",\n        \"@io_k8s_apiextensions_apiserver//pkg/client/clientset/clientset:go_default_library\",\n        \"@io_k8s_apiextensions_apiserver//pkg/client/informers/externalversions:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//dynamic:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n        \"@io_k8s_client_go//util/workqueue:go_default_library\",\n        \"@io_k8s_klog//:go_default_library\",\n        \"@io_opencensus_go//plugin/ochttp:go_default_library\",\n        \"@io_opencensus_go//stats:go_default_library\",\n        \"@io_opencensus_go//stats/view:go_default_library\",\n        \"@io_opencensus_go//tag:go_default_library\",\n        \"@io_opencensus_go//zpages:go_default_library\",\n        \"@io_opencensus_go_contrib_exporter_prometheus//:go_default_library\",\n        \"@org_golang_x_net//context:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n        \"@org_golang_x_oauth2//google:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\n        \"health_test.go\",\n        \"main_test.go\",\n        \"syncer_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@com_github_google_go_cmp//cmp:go_default_library\",\n        \"@com_github_onsi_gomega//:go_default_library\",\n        \"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:go_default_library\",\n        \"@io_k8s_apiextensions_apiserver//pkg/client/clientset/clientset/fake:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//dynamic/fake:go_default_library\",\n        \"@io_k8s_client_go//testing:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n        \"@io_k8s_client_go//util/workqueue:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"cr-syncer-app\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n)\n\npkg_tar(\n    name = \"cr-syncer-image-layer\",\n    srcs = [\":cr-syncer-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"cr-syncer-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/cr-syncer-app\"],\n    tars = [\":cr-syncer-image-layer\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/health.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"net/http\"\n\n\t\"github.com/googlecloudrobotics/ilog\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic\"\n)\n\nvar (\n\t// gvr defines which resource we expect to be able to list in the\n\t// remote cluster. We check for robottypes as the cr-syncer may only be\n\t// authorized to list syncable & unfiltered resources.\n\tgvr = schema.GroupVersionResource{\n\t\tGroup:    \"registry.cloudrobotics.com\",\n\t\tVersion:  \"v1alpha1\",\n\t\tResource: \"robottypes\",\n\t}\n)\n\n// handler handles HTTP health requests.\ntype handler struct {\n\tctx    context.Context\n\tclient dynamic.Interface\n}\n\nfunc newHealthHandler(ctx context.Context, client dynamic.Interface) http.Handler {\n\treturn &handler{ctx, client}\n}\n\nfunc (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// A simple health check: see if we can execute a list request against\n\t// the apiserver. This might block for a while or fail due to transient\n\t// network issues, so the liveness probe will need to be tolerant of\n\t// slow or flaky responses.\n\t//\n\t// If this becomes a problem, we could do the requests in the\n\t// background and just check the status of the latest request here.\n\tif _, err := h.client.Resource(gvr).List(h.ctx, metav1.ListOptions{Limit: 1}); k8serrors.IsUnauthorized(err) {\n\t\tslog.Error(\"failed health check\", ilog.Err(err))\n\t\thttp.Error(w, \"unhealthy\", http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/health_test.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic/fake\"\n\tk8stest \"k8s.io/client-go/testing\"\n)\n\nfunc TestHealthy(t *testing.T) {\n\tclient := fake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(),\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\tgvr: \"RobotList\",\n\t\t},\n\t)\n\th := newHealthHandler(context.Background(), client)\n\tts := httptest.NewServer(h)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\tt.Errorf(\"GET / returned status %d, want %d\", res.StatusCode, http.StatusOK)\n\t}\n}\n\nfunc TestHealthyForBadRequest(t *testing.T) {\n\tclient := fake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(),\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\tgvr: \"RobotList\",\n\t\t},\n\t)\n\t// To avoid unwanted crashes, we should return \"healthy\" for misc errors.\n\tclient.PrependReactor(\"*\", \"*\", func(k8stest.Action) (bool, runtime.Object, error) {\n\t\treturn true, nil, k8serrors.NewBadRequest(\"\")\n\t})\n\th := newHealthHandler(context.Background(), client)\n\tts := httptest.NewServer(h)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/health\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\tt.Errorf(\"GET / returned status %d, want %d\", res.StatusCode, http.StatusOK)\n\t}\n}\n\nfunc TestUnhealthy(t *testing.T) {\n\tclient := fake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(),\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\tgvr: \"RobotList\",\n\t\t},\n\t)\n\t// If the token vendor gives us a bad token, we might get Unauthorized errors.\n\t// https://github.com/googlecloudrobotics/core/issues/59\n\tclient.PrependReactor(\"*\", \"*\", func(k8stest.Action) (bool, runtime.Object, error) {\n\t\treturn true, nil, k8serrors.NewUnauthorized(\"\")\n\t})\n\th := newHealthHandler(context.Background(), client)\n\tts := httptest.NewServer(h)\n\tdefer ts.Close()\n\n\tres, err := http.Get(ts.URL + \"/health\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif res.StatusCode != http.StatusInternalServerError {\n\t\tt.Errorf(\"GET / returned status %d, want %d\", res.StatusCode, http.StatusInternalServerError)\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// The CR syncer syncs custom resources between a remote Kubernetes cluster and\n// the local Kubernetes cluster. The spec part is copied from upstream to\n// downstream, and the status part is copied from downstream to upstream.\n//\n// The behaviour can be customized by annotations on the CRDs.\n//\n// Annotation \"filter-by-robot-name\"\n//\n//\tcr-syncer.cloudrobotics.com/filter-by-robot-name: <bool>\n//\n// If true, only sync CRs that have a label 'cloudrobotics.com/robot-name:\n// <robot-name>' that matches the robot-name arg given on the command line.\n//\n// Annotation \"status-subtree\"\n//\n//\tcr-syncer.cloudrobotics.com/status-subtree: <string>\n//\n// If specified, only sync the given subtree of the Status field. This is useful\n// if resources have a shared status.\n//\n// Annotation \"spec-source\"\n//\n//\tcr-syncer.cloudrobotics.com/spec-source: <string>\n//\n// If set to \"cloud\", the source of truth for object existence and specs\n// (upstream) is the remote cluster and for status it's local (downstream).\n// If set to \"\", the CRD is ignored.\n//\n// NOTE: Previously, this could be set to \"robot\", but support was removed as it\n// was unused and the required auth setup is more complex, and would need\n// changes to cr-syncer-auth-webhook to validate CR creation as well.\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"contrib.go.opencensus.io/exporter/prometheus\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/motemen/go-loghttp\"\n\t\"go.opencensus.io/plugin/ochttp\"\n\t\"go.opencensus.io/stats/view\"\n\t\"go.opencensus.io/tag\"\n\t\"go.opencensus.io/zpages\"\n\t\"golang.org/x/net/context\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\tcrdtypes \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tcrdclientset \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset\"\n\tcrdinformer \"k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/klog\"\n)\n\nconst (\n\t// Resync informers every 5 minutes. This will cause all current resources\n\t// to be sent as updates once again, which will trigger reconciliation on those\n\t// objects and thus fix any potential drift.\n\tresyncPeriod = 5 * time.Minute\n)\n\nvar (\n\tremoteServer       = flag.String(\"remote-server\", \"\", \"Remote Kubernetes server\")\n\trobotName          = flag.String(\"robot-name\", \"\", \"Robot we are running on, can be used for selective syncing\")\n\tlistenAddr         = flag.String(\"listen-address\", \":80\", \"HTTP listen address\")\n\tconflictErrorLimit = flag.Int(\"conflict-error-limit\", 5, \"Number of consecutive conflict errors before informer is restarted\")\n\ttimeout            = flag.Int64(\"timeout\", 300, \"Timeout for CR watch calls in seconds\")\n\tuseRobotJWT        = flag.Bool(\"use-robot-jwt\", false, \"Use robot JWT for authn instead of GCP access token - requires recent CRC cloud deployment\")\n\tverbose            = flag.Bool(\"verbose\", false, \"DEPRECATED: Use log_level\")\n\tlogLevel           = flag.Int(\"log-level\", int(slog.LevelInfo), \"the log message level required to be logged\")\n\n\tsizeDistribution    = view.Distribution(0, 1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 33554432)\n\tlatencyDistribution = view.Distribution(0, 1, 2, 5, 10, 15, 25, 50, 100, 200, 400, 800, 1500, 3000, 6000)\n\n\ttagLocation = mustNewTagKey(\"location\")\n)\n\nfunc init() {\n\tif err := view.Register(\n\t\t&view.View{\n\t\t\tName:        ochttp.ClientRequestCount.Name(),\n\t\t\tDescription: ochttp.ClientRequestCount.Description(),\n\t\t\tMeasure:     ochttp.ClientRequestCount,\n\t\t\tTagKeys:     []tag.Key{ochttp.Method, tagLocation},\n\t\t\tAggregation: view.Count(),\n\t\t},\n\t\t&view.View{\n\t\t\tName:        ochttp.ClientRequestBytes.Name(),\n\t\t\tDescription: ochttp.ClientRequestBytes.Description(),\n\t\t\tMeasure:     ochttp.ClientRequestBytes,\n\t\t\tTagKeys:     []tag.Key{ochttp.Method, ochttp.StatusCode, tagLocation},\n\t\t\tAggregation: sizeDistribution,\n\t\t},\n\t\t&view.View{\n\t\t\tName:        ochttp.ClientResponseBytes.Name(),\n\t\t\tDescription: ochttp.ClientResponseBytes.Description(),\n\t\t\tMeasure:     ochttp.ClientResponseBytes,\n\t\t\tTagKeys:     []tag.Key{ochttp.Method, ochttp.StatusCode, tagLocation},\n\t\t\tAggregation: sizeDistribution,\n\t\t},\n\t\t&view.View{\n\t\t\tName:        ochttp.ClientLatency.Name(),\n\t\t\tDescription: ochttp.ClientLatency.Description(),\n\t\t\tMeasure:     ochttp.ClientLatency,\n\t\t\tTagKeys:     []tag.Key{ochttp.Method, ochttp.StatusCode, tagLocation},\n\t\t\tAggregation: latencyDistribution,\n\t\t},\n\t); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// PrefixingRoundtripper is a HTTP roundtripper that adds a specified prefix to\n// all HTTP requests. We need to use it instead of setting APIPath because\n// autogenerated and dynamic Kubernetes clients overwrite the REST config's\n// APIPath.\ntype PrefixingRoundtripper struct {\n\tPrefix string\n\tBase   http.RoundTripper\n}\n\nfunc (pr *PrefixingRoundtripper) RoundTrip(r *http.Request) (*http.Response, error) {\n\t// Avoid an extra roundtrip for the protocol upgrade\n\tr.URL.Scheme = \"https\"\n\tif !strings.HasPrefix(r.URL.Path, pr.Prefix+\"/\") {\n\t\tr.URL.Path = pr.Prefix + r.URL.Path\n\t}\n\tresp, err := pr.Base.RoundTrip(r)\n\treturn resp, err\n}\n\n// ctxRoundTripper injects a fixed context into all requests. This is used to\n// provide static OpenCensus tags as Kubernetes' client-go provides no context hooks.\ntype ctxRoundTripper struct {\n\tbase http.RoundTripper\n\tctx  context.Context\n}\n\nfunc (r *ctxRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn r.base.RoundTrip(req.WithContext(r.ctx))\n}\n\n// restConfigForRemote assembles the K8s REST config for the remote server.\nfunc restConfigForRemote(ctx context.Context) (*rest.Config, error) {\n\tvar tokenSource oauth2.TokenSource\n\tvar err error\n\tif *useRobotJWT {\n\t\ttokenSource = robotauth.CreateJWTSource()\n\t} else {\n\t\ttokenSource, err = google.DefaultTokenSource(ctx, \"https://www.googleapis.com/auth/cloud-platform\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tctx, err = tag.New(ctx, tag.Insert(tagLocation, \"remote\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttransport := func(base http.RoundTripper) (rt http.RoundTripper) {\n\t\trt = &oauth2.Transport{\n\t\t\tSource: tokenSource,\n\t\t\tBase:   base,\n\t\t}\n\t\trt = &PrefixingRoundtripper{\n\t\t\tPrefix: \"/apis/core.kubernetes\",\n\t\t\tBase:   rt,\n\t\t}\n\t\tif *verbose {\n\t\t\trt = &loghttp.Transport{Transport: rt}\n\t\t}\n\t\trt = &ochttp.Transport{Base: rt}\n\t\treturn &ctxRoundTripper{base: rt, ctx: ctx}\n\t}\n\treturn &rest.Config{\n\t\tHost:          *remoteServer,\n\t\tAPIPath:       \"/apis\",\n\t\tWrapTransport: transport,\n\t\t// The original value of timeout is set in the options of lister and watcher in newInformer function. This timeout is not enforced by the client.\n\t\t// That's the reason for the timeout in REST config. It is set to timeout + 5 seconds to give some time for a graceful closing of the connection.\n\t\tTimeout: time.Second * (time.Duration(*timeout) + 5),\n\t}, nil\n}\n\ntype CrdChange struct {\n\tType watch.EventType\n\tCRD  *crdtypes.CustomResourceDefinition\n}\n\nfunc streamCrds(done <-chan struct{}, clientset crdclientset.Interface, crds chan<- CrdChange) error {\n\tfactory := crdinformer.NewSharedInformerFactory(clientset, 0)\n\tinformer := factory.Apiextensions().V1().CustomResourceDefinitions().Informer()\n\n\tgo informer.Run(done)\n\n\tslog.Info(\"Syncing cache for CRDs\")\n\tok := cache.WaitForCacheSync(done, informer.HasSynced)\n\tif !ok {\n\t\treturn fmt.Errorf(\"WaitForCacheSync failed\")\n\t}\n\n\tinformer.AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\tcrds <- CrdChange{Type: watch.Added, CRD: obj.(*crdtypes.CustomResourceDefinition)}\n\t\t},\n\t\tUpdateFunc: func(oldObj, newObj interface{}) {\n\t\t\tcrds <- CrdChange{Type: watch.Modified, CRD: newObj.(*crdtypes.CustomResourceDefinition)}\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tcrds <- CrdChange{Type: watch.Deleted, CRD: obj.(*crdtypes.CustomResourceDefinition)}\n\t\t},\n\t})\n\treturn nil\n}\n\nfunc main() {\n\tklog.InitFlags(nil)\n\tflag.Parse()\n\tctx := context.Background()\n\n\tll := slog.Level(*logLevel)\n\tif *verbose {\n\t\tll = slog.LevelDebug\n\t}\n\tlogHandler := ilog.NewLogHandler(ll, os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tlocalConfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tslog.Error(\"InClusterConfig\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tlocalCtx, err := tag.New(ctx, tag.Insert(tagLocation, \"local\"))\n\tif err != nil {\n\t\tslog.Error(\"tag.New\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tlocalConfig.WrapTransport = func(base http.RoundTripper) http.RoundTripper {\n\t\tif *verbose {\n\t\t\tbase = &loghttp.Transport{Transport: base}\n\t\t}\n\t\tbase = &ochttp.Transport{Base: base}\n\t\treturn &ctxRoundTripper{base: base, ctx: localCtx}\n\t}\n\tlocal, err := dynamic.NewForConfig(localConfig)\n\tif err != nil {\n\t\tslog.Error(\"NewForConfig\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tremoteConfig, err := restConfigForRemote(ctx)\n\tif err != nil {\n\t\tslog.Error(\"restConfigForRemote\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tremote, err := dynamic.NewForConfig(remoteConfig)\n\tif err != nil {\n\t\tslog.Error(\"NewForConfig\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\texporter, err := prometheus.NewExporter(prometheus.Options{})\n\tif err != nil {\n\t\tslog.Error(\"NewExporter\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tview.RegisterExporter(exporter)\n\tview.SetReportingPeriod(time.Second)\n\tzpages.Handle(nil, \"/debug\")\n\thttp.Handle(\"/metrics\", exporter)\n\thttp.Handle(\"/health\", newHealthHandler(ctx, remote))\n\n\tgo func() {\n\t\tif err := http.ListenAndServe(*listenAddr, nil); err != nil {\n\t\t\tslog.Error(\"ListenAndServe\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}()\n\n\tcrds := make(chan CrdChange)\n\tif err := streamCrds(ctx.Done(), crdclientset.NewForConfigOrDie(localConfig), crds); err != nil {\n\t\tslog.Error(\"Unable to stream CRDs from local Kubernetes\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tsyncers := make(map[string]*crSyncer)\n\tfor crd := range crds {\n\t\tname := crd.CRD.GetName()\n\n\t\tif cur, ok := syncers[name]; ok {\n\t\t\tif crd.Type == watch.Added {\n\t\t\t\tslog.Warn(\"Already had a running sync\", slog.String(\"syncer\", name))\n\t\t\t}\n\t\t\tcur.stop()\n\t\t\tdelete(syncers, name)\n\t\t}\n\t\tif crd.Type == watch.Added || crd.Type == watch.Modified {\n\t\t\t// The modify procedure is very heavyweight: We throw away\n\t\t\t// the informer for the CRD (read: all cached data) on every\n\t\t\t// modification and recreate it. If that ever turns out to\n\t\t\t// be a problem, we should use a shared informer cache\n\t\t\t// instead.\n\t\t\ts, err := newCRSyncer(ctx, *crd.CRD, local, remote, *robotName)\n\t\t\tif err != nil {\n\t\t\t\tif err != errIgnoredCRD {\n\t\t\t\t\tslog.Error(\"skipping custom resource\", slog.String(\"Resource\", name), ilog.Err(err))\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsyncers[name] = s\n\t\t\tgo s.run()\n\t\t}\n\t}\n}\n\nfunc mustNewTagKey(s string) tag.Key {\n\tk, err := tag.NewKey(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn k\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/main_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/gomega\"\n\tcrdtypes \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tfakecrdclientset \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n)\n\nfunc TestStreamCrdsSeesPreexistingObject(t *testing.T) {\n\tg := NewGomegaWithT(t)\n\titems := []runtime.Object{\n\t\t&crdtypes.CustomResourceDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:            \"foo\",\n\t\t\t\tResourceVersion: \"1\",\n\t\t\t},\n\t\t},\n\t}\n\tcs := fakecrdclientset.NewSimpleClientset(items...)\n\n\tvar wg sync.WaitGroup\n\tcrds := make(chan CrdChange)\n\tdone := make(chan struct{})\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tselect {\n\t\tcase crd := <-crds:\n\t\t\tg.Expect(crd.Type).To(Equal(watch.Added))\n\t\t\tg.Expect(crd.CRD.GetName()).To(Equal(\"foo\"))\n\t\tcase <-time.After(15 * time.Second):\n\t\t\tt.Errorf(\"Received no watch event; wanted add for foo\")\n\t\t}\n\t\tclose(done)\n\t}()\n\tif err := streamCrds(done, cs, crds); err != nil {\n\t\tt.Errorf(\"Got unexpected error: %v\", err)\n\t}\n\twg.Wait()\n}\n\nfunc TestStreamCrdsSeesAdditionAndDeletion(t *testing.T) {\n\tctx := context.Background()\n\tg := NewGomegaWithT(t)\n\tcs := fakecrdclientset.NewSimpleClientset()\n\n\tcrds := make(chan CrdChange)\n\tdone := make(chan struct{})\n\tdefer close(done)\n\n\tif err := streamCrds(done, cs, crds); err != nil {\n\t\tt.Errorf(\"Got unexpected error: %v\", err)\n\t}\n\n\tcs.ApiextensionsV1().CustomResourceDefinitions().Create(ctx,\n\t\t&crdtypes.CustomResourceDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: \"later\",\n\t\t\t},\n\t\t},\n\t\tmetav1.CreateOptions{})\n\tselect {\n\tcase crd := <-crds:\n\t\tg.Expect(crd.Type).To(Equal(watch.Added))\n\t\tg.Expect(crd.CRD.GetName()).To(Equal(\"later\"))\n\tcase <-time.After(15 * time.Second):\n\t\tt.Errorf(\"Received no watch event; wanted add for later\")\n\t}\n\n\tcs.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, \"later\", metav1.DeleteOptions{})\n\tselect {\n\tcase crd := <-crds:\n\t\tg.Expect(crd.Type).To(Equal(watch.Deleted))\n\t\tg.Expect(crd.CRD.GetName()).To(Equal(\"later\"))\n\tcase <-time.After(15 * time.Second):\n\t\tt.Errorf(\"Received no watch event; wanted deleted for later\")\n\t}\n}\n\nfunc TestStreamCrdsSeesUpdate(t *testing.T) {\n\tctx := context.Background()\n\tg := NewGomegaWithT(t)\n\tcs := fakecrdclientset.NewSimpleClientset()\n\n\tcrds := make(chan CrdChange)\n\tdone := make(chan struct{})\n\tdefer close(done)\n\n\tif err := streamCrds(done, cs, crds); err != nil {\n\t\tt.Errorf(\"Got unexpected error: %v\", err)\n\t}\n\n\tcs.ApiextensionsV1().CustomResourceDefinitions().Create(ctx,\n\t\t&crdtypes.CustomResourceDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: \"later\",\n\t\t\t},\n\t\t},\n\t\tmetav1.CreateOptions{})\n\tselect {\n\tcase crd := <-crds:\n\t\tg.Expect(crd.Type).To(Equal(watch.Added))\n\t\tg.Expect(crd.CRD.GetName()).To(Equal(\"later\"))\n\tcase <-time.After(15 * time.Second):\n\t\tt.Errorf(\"Received no watch event; wanted add for later\")\n\t}\n\n\tcs.ApiextensionsV1().CustomResourceDefinitions().Update(ctx,\n\t\t&crdtypes.CustomResourceDefinition{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: \"later\",\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\"foo\": \"bar\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tmetav1.UpdateOptions{})\n\tselect {\n\tcase crd := <-crds:\n\t\tg.Expect(crd.Type).To(Equal(watch.Modified))\n\t\tg.Expect(crd.CRD.GetName()).To(Equal(\"later\"))\n\tcase <-time.After(15 * time.Second):\n\t\tt.Errorf(\"Received no watch event; wanted modified for later\")\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/syncer.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\t\"go.opencensus.io/stats\"\n\t\"go.opencensus.io/stats/view\"\n\t\"go.opencensus.io/tag\"\n\tcrdtypes \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\nconst (\n\t// Annotations attached to CRDs.\n\tannotationStatusSubtree     = \"cr-syncer.cloudrobotics.com/status-subtree\"\n\tannotationFilterByRobotName = \"cr-syncer.cloudrobotics.com/filter-by-robot-name\"\n\tannotationSpecSource        = \"cr-syncer.cloudrobotics.com/spec-source\"\n\n\t// Annotations and labels attached to CRs.\n\tlabelRobotName = \"cloudrobotics.com/robot-name\"\n\t// Annotation for remote resource version. Note that for resources in\n\t// the cloud cluster, this is a resource version on the robot's cluster\n\t// (and vice versa). This will only be set when the status subresource\n\t// is disabled, otherwise the status and annotation cannot be updated\n\t// in a single request.\n\tannotationResourceVersion = \"cr-syncer.cloudrobotics.com/remote-resource-version\"\n\n\tcloudClusterName = \"cloud\"\n)\n\nvar (\n\tmSyncs = stats.Int64(\n\t\t\"cr-syncer.cloudrobotics.com/syncs\",\n\t\t\"Synchronizations triggered by resource events\",\n\t\tstats.UnitDimensionless,\n\t)\n\tmSyncErrors = stats.Int64(\n\t\t\"cr-syncer.cloudrobotics.com/sync_errors\",\n\t\t\"Synchronization errors on resource events\",\n\t\tstats.UnitDimensionless,\n\t)\n\ttagEventSource = mustNewTagKey(\"event_source\")\n\ttagResource    = mustNewTagKey(\"resource\")\n\n\t// errIgnoredCRD indicates that the spec-source label is missing or empty\n\t// and this CRD should be ignored.\n\terrIgnoredCRD = errors.New(\"this CRD is not synced\")\n)\n\nfunc init() {\n\tif err := view.Register(\n\t\t&view.View{\n\t\t\tName:        \"cr-syncer.cloudrobotics.com/syncs_total\",\n\t\t\tDescription: \"Total number of synchronizations triggered resource events\",\n\t\t\tMeasure:     mSyncs,\n\t\t\tTagKeys:     []tag.Key{tagEventSource, tagResource},\n\t\t\tAggregation: view.Count(),\n\t\t},\n\t\t&view.View{\n\t\t\tName:        \"cr-syncer.cloudrobotics.com/sync_errors_total\",\n\t\t\tDescription: \"Total number of synchronizations errors on resource events\",\n\t\t\tMeasure:     mSyncErrors,\n\t\t\tTagKeys:     []tag.Key{tagEventSource, tagResource},\n\t\t\tAggregation: view.Count(),\n\t\t},\n\t); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// removeFinalizer removes the cr-syncer finalizer for this robot. Finalizers\n// for offline robots have to be removed manually (eg with `kubectl edit`).\n// TODO(rodrigoq): remove after migration\nfunc removeFinalizer(ctx context.Context, client dynamic.ResourceInterface, obj *unstructured.Unstructured, clusterName string) {\n\tupdate := false\n\tthisFinalizer := fmt.Sprintf(\"%s.synced.cr-syncer.cloudrobotics.com\", clusterName)\n\tfinalizers := []string{}\n\tfor _, x := range obj.GetFinalizers() {\n\t\tif x == thisFinalizer {\n\t\t\tupdate = true\n\t\t} else {\n\t\t\tfinalizers = append(finalizers, x)\n\t\t}\n\t}\n\tif !update {\n\t\treturn\n\t}\n\tobj.SetFinalizers(finalizers)\n\tif _, err := client.Update(ctx, obj, metav1.UpdateOptions{}); err != nil {\n\t\tif isNotFoundError(err) {\n\t\t\treturn\n\t\t}\n\t\tslog.Error(\"failed to remove finalizers\", ilog.Err(err))\n\t}\n}\n\n// crSyncer synchronizes custom resources from an upstream source cluster to a\n// downstream cluster.\n// Updates to the status subresource in the downstream are propagated back to\n// the upstream cluster.\ntype crSyncer struct {\n\tctx           context.Context\n\tclusterName   string // Name of downstream cluster.\n\tcrd           crdtypes.CustomResourceDefinition\n\tupstream      dynamic.ResourceInterface // Source of the spec.\n\tdownstream    dynamic.ResourceInterface // Source of the status.\n\tlabelSelector string\n\tsubtree       string\n\tversionIx     int\n\n\t// Informers and the queues they feed. Upstream/downstream describes\n\t// the source of the change events, _not_ the direction they are heading.\n\t// For example, upstream{Inf,Queue} receive updates that will result in the\n\t// syncer taking actions against the downstream cluster.\n\tupstreamInf     cache.SharedIndexInformer\n\tdownstreamInf   cache.SharedIndexInformer\n\tupstreamQueue   workqueue.RateLimitingInterface\n\tdownstreamQueue workqueue.RateLimitingInterface\n\tinfDone         chan struct{}\n\n\tconflictErrors int\n\n\tdone chan struct{} // Terminates all background processes.\n}\n\nfunc getStorageVersionIndex(crd crdtypes.CustomResourceDefinition) (int, error) {\n\tfor ix, v := range crd.Spec.Versions {\n\t\tif v.Storage {\n\t\t\treturn ix, nil\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"Invalid Custom Resource %s: no version with stored=true set\", crd.ObjectMeta.Name)\n}\n\nfunc newCRSyncer(\n\tctx context.Context,\n\tcrd crdtypes.CustomResourceDefinition,\n\tlocal, remote dynamic.Interface,\n\trobotName string,\n) (*crSyncer, error) {\n\tvar (\n\t\tannotations        = crd.ObjectMeta.Annotations\n\t\tfilterByRobotValue = annotations[annotationFilterByRobotName]\n\t\tfilterByRobot      = false\n\t)\n\tif filterByRobotValue != \"\" {\n\t\tif v, err := strconv.ParseBool(filterByRobotValue); err != nil {\n\t\t\tslog.Error(\"Value must be boolean\",\n\t\t\t\tslog.String(\"Filter\", annotationFilterByRobotName),\n\t\t\t\tslog.String(\"Target\", crd.ObjectMeta.Name),\n\t\t\t\tslog.String(\"Got\", filterByRobotValue))\n\t\t} else {\n\t\t\tfilterByRobot = v\n\t\t}\n\t}\n\tversionIx, err := getStorageVersionIndex(crd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"Bad crd passed to newCRSyncer\")\n\t}\n\n\tgvr := schema.GroupVersionResource{\n\t\tGroup:    crd.Spec.Group,\n\t\tVersion:  crd.Spec.Versions[versionIx].Name,\n\t\tResource: crd.Spec.Names.Plural,\n\t}\n\tns := \"\"\n\tif crd.Spec.Scope == crdtypes.NamespaceScoped {\n\t\t// TODO(https://github.com/googlecloudrobotics/core/issues/19): allow syncing CRs in other namespaces\n\t\tns = \"default\"\n\t}\n\ts := &crSyncer{\n\t\tctx:        ctx,\n\t\tcrd:        crd,\n\t\tsubtree:    annotations[annotationStatusSubtree],\n\t\tversionIx:  versionIx,\n\t\tupstream:   remote.Resource(gvr).Namespace(ns),\n\t\tdownstream: local.Resource(gvr).Namespace(ns),\n\t\tdone:       make(chan struct{}),\n\t}\n\tswitch src := annotations[annotationSpecSource]; src {\n\tcase \"\":\n\t\treturn nil, errIgnoredCRD\n\tcase \"cloud\":\n\t\ts.clusterName = fmt.Sprintf(\"robot-%s\", robotName)\n\t\t// Use DefaultControllerRateLimiter for queue with destination robot and ItemFastSlowRateLimiter for queue with destination cloud to improve resilience regarding network errors\n\t\t// Upstream destination is robot cluster, downstream destination is cloud cluster\n\t\ts.upstreamQueue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), \"upstream\")\n\t\ts.downstreamQueue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(time.Millisecond*500, time.Second*5, 5), \"downstream\")\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown spec source %q\", src)\n\t}\n\tif filterByRobot {\n\t\tif robotName != \"\" {\n\t\t\ts.labelSelector = labelRobotName + \"=\" + robotName\n\t\t} else {\n\t\t\t// TODO(fabxc): should this return an error instead?\n\t\t\tslog.Warn(\"request to filter by robot-name, but no robot-name was given to cr-syncer\", slog.String(\"Requester\", crd.ObjectMeta.Name))\n\t\t}\n\t}\n\n\ts.upstreamInf = s.newInformer(s.upstream)\n\ts.downstreamInf = s.newInformer(s.downstream)\n\n\treturn s, nil\n}\n\nfunc (s *crSyncer) newInformer(client dynamic.ResourceInterface) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options metav1.ListOptions) (runtime.Object, error) {\n\t\t\t\toptions.LabelSelector = s.labelSelector\n\t\t\t\toptions.TimeoutSeconds = timeout\n\t\t\t\treturn client.List(s.ctx, options)\n\t\t\t},\n\t\t\tWatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {\n\t\t\t\toptions.LabelSelector = s.labelSelector\n\t\t\t\toptions.TimeoutSeconds = timeout\n\t\t\t\treturn client.Watch(s.ctx, options)\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{},\n\t\tresyncPeriod,\n\t\tnil,\n\t)\n}\n\nfunc (s *crSyncer) startInformers() error {\n\tif s.infDone != nil {\n\t\treturn fmt.Errorf(\"informer for %s already started\", s.crd.GetName())\n\t}\n\ts.infDone = make(chan struct{})\n\n\tgo s.upstreamInf.Run(s.infDone)\n\tgo s.downstreamInf.Run(s.infDone)\n\n\tif ok := cache.WaitForCacheSync(s.infDone, s.upstreamInf.HasSynced); !ok {\n\t\treturn fmt.Errorf(\"stopped while syncing upstream informer for %s\", s.crd.GetName())\n\t}\n\tif ok := cache.WaitForCacheSync(s.infDone, s.downstreamInf.HasSynced); !ok {\n\t\treturn fmt.Errorf(\"stopped while syncing downstream informer for %s\", s.crd.GetName())\n\t}\n\ts.setupInformerHandlers(s.upstreamInf, s.upstreamQueue, \"upstream\")\n\ts.setupInformerHandlers(s.downstreamInf, s.downstreamQueue, \"downstream\")\n\n\treturn nil\n}\n\nfunc (s *crSyncer) stopInformers() {\n\tif s.infDone != nil {\n\t\tclose(s.infDone)\n\t\ts.infDone = nil\n\t}\n}\n\nfunc (s *crSyncer) restartInformers() error {\n\ts.stopInformers()\n\ts.upstreamInf = s.newInformer(s.upstream)\n\ts.downstreamInf = s.newInformer(s.downstream)\n\treturn s.startInformers()\n}\n\nfunc (s *crSyncer) setupInformerHandlers(\n\tinf cache.SharedIndexInformer,\n\tqueue workqueue.RateLimitingInterface,\n\tdirection string,\n) {\n\treceive := func(obj interface{}, action string) {\n\t\tu := obj.(*unstructured.Unstructured)\n\t\tslog.Debug(\"Got Event\",\n\t\t\tslog.String(\"Event\", action),\n\t\t\tslog.String(\"Direction\", direction),\n\t\t\tslog.String(\"Kind\", u.GetKind()),\n\t\t\tslog.String(\"Name\", u.GetName()),\n\t\t\tslog.String(\"Version\", u.GetResourceVersion()))\n\t\tif key, ok := keyFunc(obj); ok {\n\t\t\tqueue.AddRateLimited(key)\n\t\t}\n\t}\n\tinf.AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\treceive(obj, \"add\")\n\t\t},\n\t\tUpdateFunc: func(_, obj interface{}) {\n\t\t\treceive(obj, \"update\")\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\treceive(obj, \"delete\")\n\t\t},\n\t})\n}\n\nfunc (s *crSyncer) processNextWorkItem(\n\tctx context.Context,\n\tq workqueue.RateLimitingInterface,\n\tsyncf func(string) error,\n\tqName string,\n) bool {\n\tkey, quit := q.Get()\n\tif quit {\n\t\treturn false\n\t}\n\tdefer q.Done(key)\n\n\t// Restart informers on too many conflict errors\n\t// client-go does not reliably recognize when watch calls are closed by remote API server\n\t// cr-syncer is able to detect that when updating CRs on remote API server when there are multiple subsequent conflict errors (HTTP 409)\n\t// like \"...please apply your changes to the latest version and try again\"\n\t// This could occur at watchers of single CRDs while others keep working. Thus, it is less resource intensive just restarting informers of the affected CRDs rather than whoel cr-syncer\n\t// Errors are counted in syncUpstream and syncDownstream functions\n\tif s.conflictErrors >= *conflictErrorLimit {\n\t\tslog.Info(\"Restarting informers because of too many conflict errors\", slog.String(\"CRD\", s.crd.GetName()))\n\t\terr := s.restartInformers()\n\t\tif err != nil {\n\t\t\tslog.Warn(\"Restarting informers failed\", slog.String(\"CRD\", s.crd.GetName()))\n\t\t\tq.AddRateLimited(key)\n\t\t\treturn true\n\t\t} else {\n\t\t\ts.conflictErrors = 0\n\t\t}\n\t}\n\n\tctx, err := tag.New(ctx, tag.Insert(tagEventSource, qName))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\terr = syncf(key.(string))\n\tstats.Record(ctx, mSyncs.M(1))\n\tif err == nil {\n\t\tq.Forget(key)\n\t\treturn true\n\t}\n\t// Synchronization failed, retry later.\n\tstats.Record(ctx, mSyncErrors.M(1))\n\tslog.Warn(\"Syncing key from queue failed\",\n\t\tslog.Any(\"Key\", key),\n\t\tslog.String(\"Queue\", qName),\n\t\tilog.Err(err))\n\tq.AddRateLimited(key)\n\n\treturn true\n}\n\nfunc (s *crSyncer) run() {\n\tdefer s.upstreamQueue.ShutDown()\n\tdefer s.downstreamQueue.ShutDown()\n\n\tslog.Info(\"Starting syncer\", slog.String(\"CRD\", s.crd.GetName()))\n\n\t// Start informers that will populate their associated workqueue.\n\tif err := s.startInformers(); err != nil {\n\t\tslog.Warn(\"Starting informers failed\", slog.String(\"CRD\", s.crd.GetName()), ilog.Err(err))\n\t\treturn\n\t}\n\n\tctx, err := tag.New(context.Background(), tag.Insert(tagResource, s.crd.Name))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// Process the upstream and downstream work queues.\n\tgo func() {\n\t\tfor s.processNextWorkItem(ctx, s.upstreamQueue, s.syncUpstream, \"upstream\") {\n\t\t}\n\t}()\n\tgo func() {\n\t\tfor s.processNextWorkItem(ctx, s.downstreamQueue, s.syncDownstream, \"downstream\") {\n\t\t}\n\t}()\n\t<-s.done\n\t// Close informers\n\tif s.infDone != nil {\n\t\tclose(s.infDone)\n\t}\n}\n\nfunc (s *crSyncer) stop() {\n\tslog.Info(\"Stopping syncer\", slog.String(\"CRD\", s.crd.GetName()))\n\tclose(s.done)\n}\n\n// syncDownstream reconciles state after receiving change events from the\n// downstream cluster. It synchronizes the status from the downstream to the\n// upstream cluster, and deletes orphaned downstream resources.\nfunc (s *crSyncer) syncDownstream(key string) error {\n\tv := s.crd.Spec.Versions[s.versionIx]\n\tstatusIsSubresource := v.Subresources != nil && v.Subresources.Status != nil\n\t// Get the downstream status (src) and upstream spec (dst).\n\tsrcObj, srcExists, err := s.downstreamInf.GetIndexer().GetByKey(key)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve resource for key %s: %s\", key, err)\n\t}\n\tif !srcExists {\n\t\t// The downstream resource has been deleted: possibly because\n\t\t// the upstream resource was deleted and recreated. Add this to\n\t\t// the upstream queue so that syncUpstream() can check if it needs\n\t\t// to recreate the downstream resource.\n\t\ts.upstreamQueue.Add(key)\n\t\treturn nil\n\t}\n\tsrc := srcObj.(*unstructured.Unstructured).DeepCopy()\n\tremoveFinalizer(s.ctx, s.downstream, src, s.clusterName)\n\n\tdstObj, dstExists, err := s.upstreamInf.GetIndexer().GetByKey(key)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve resource for key %s: %s\", key, err)\n\t}\n\t// If the upstream resource no longer exists, delete the downstream\n\t// resource. Normally, this occurs when syncUpstream() handles the\n\t// upstream deletion, but if the resource was deleted when the robot\n\t// was offline, upstream doesn't know about the old resource and we'll\n\t// hit this condition.\n\tif !dstExists {\n\t\tif src.GetDeletionTimestamp() != nil {\n\t\t\treturn nil // Already being deleted.\n\t\t}\n\t\tif err := s.downstream.Delete(s.ctx, src.GetName(), metav1.DeleteOptions{}); err != nil {\n\t\t\tif isNotFoundError(err) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"delete resource: %s\", err)\n\t\t}\n\t\treturn nil\n\t}\n\tdst := dstObj.(*unstructured.Unstructured).DeepCopy()\n\n\t// Copy full status or subtree from src to dst.\n\tif s.subtree == \"\" {\n\t\tcopyStatus(dst, src)\n\t} else if src.Object[\"status\"] != nil {\n\t\tsrcStatus, ok := src.Object[\"status\"].(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"Expected status of %s in downstream cluster to be a dict\", src.GetName())\n\t\t}\n\t\tif dst.Object[\"status\"] == nil {\n\t\t\tdst.Object[\"status\"] = make(map[string]interface{})\n\t\t}\n\t\tdstStatus, ok := dst.Object[\"status\"].(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"Expected status of %s in upstream cluster to be a dict\", src.GetName())\n\t\t}\n\t\tif srcStatus[s.subtree] != nil {\n\t\t\tdstStatus[s.subtree] = srcStatus[s.subtree]\n\t\t} else {\n\t\t\tdelete(dstStatus, s.subtree)\n\t\t}\n\t}\n\tsetAnnotation(dst, annotationResourceVersion, src.GetResourceVersion())\n\n\t// We need to make a dedicated UpdateStatus call if the status is defined\n\t// as an explicit subresource of the CRD.\n\tif statusIsSubresource {\n\t\t// Status must not be null/nil.\n\t\tif dst.Object[\"status\"] == nil {\n\t\t\tdst.Object[\"status\"] = struct{}{}\n\t\t}\n\t\tupdated, err := s.upstream.UpdateStatus(s.ctx, dst, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\t// Count subsequent conflict errors\n\t\t\tif k8serrors.IsConflict(err) && s.clusterName != cloudClusterName {\n\t\t\t\ts.conflictErrors += 1\n\t\t\t}\n\t\t\treturn newAPIErrorf(dst, \"update status failed: %s\", err)\n\t\t}\n\t\tdst = updated\n\t} else {\n\t\tupdated, err := s.upstream.Update(s.ctx, dst, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\t// Count subsequent conflict errors\n\t\t\tif k8serrors.IsConflict(err) && s.clusterName != cloudClusterName {\n\t\t\t\ts.conflictErrors += 1\n\t\t\t}\n\t\t\treturn newAPIErrorf(dst, \"update failed: %s\", err)\n\t\t}\n\t\tdst = updated\n\t}\n\t// Reset error count\n\tif s.clusterName != cloudClusterName {\n\t\ts.conflictErrors = 0\n\t}\n\tslog.Debug(\"Copied status to upstream\",\n\t\tslog.String(\"Kind\", src.GetKind()),\n\t\tslog.String(\"Name\", src.GetName()),\n\t\tslog.Any(\"Source version\", src.GetResourceVersion()),\n\t\tslog.Any(\"Destination version\", dst.GetResourceVersion()))\n\n\treturn nil\n}\n\n// syncUpstream reconciles the state after receiving a change event from upstream.\n// It synchronizes the spec changes from upstream to the downstream cluster and propagates\n// deletions.\nfunc (s *crSyncer) syncUpstream(key string) error {\n\t// Get the upstream spec (src) and downstream status (dst).\n\tsrc := &unstructured.Unstructured{make(map[string]interface{})}\n\tdst := &unstructured.Unstructured{make(map[string]interface{})}\n\tsrcObj, srcExists, err := s.upstreamInf.GetIndexer().GetByKey(key)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve resource for key %s: %s\", key, err)\n\t}\n\tif srcExists {\n\t\tsrc = srcObj.(*unstructured.Unstructured).DeepCopy()\n\t\tremoveFinalizer(s.ctx, s.upstream, src, s.clusterName)\n\t}\n\tdstObj, dstExists, err := s.downstreamInf.GetIndexer().GetByKey(key)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve resource for key %s: %s\", key, err)\n\t}\n\tif dstExists {\n\t\tdst = dstObj.(*unstructured.Unstructured).DeepCopy()\n\t}\n\n\t// Check if the downstream resource (dst) should be created, updated,\n\t// or deleted. If we don't need to create/update dst, return early.\n\tvar createOrUpdate func(*unstructured.Unstructured) (*unstructured.Unstructured, error)\n\tswitch {\n\tcase !srcExists && !dstExists:\n\t\t// Both deleted, nothing to do.\n\t\treturn nil\n\tcase srcExists && !dstExists:\n\t\t// Create object and set base fields.\n\t\tcreateOrUpdate = func(o *unstructured.Unstructured) (*unstructured.Unstructured, error) {\n\t\t\to.SetGroupVersionKind(src.GroupVersionKind())\n\t\t\to.SetNamespace(src.GetNamespace())\n\t\t\to.SetName(src.GetName())\n\t\t\t// Copy upstream status on initial creation.\n\t\t\to.Object[\"status\"] = src.Object[\"status\"]\n\n\t\t\treturn s.downstream.Create(s.ctx, o, metav1.CreateOptions{})\n\t\t}\n\tcase srcExists && dstExists:\n\t\t// Update dst.\n\t\tcreateOrUpdate = func(o *unstructured.Unstructured) (*unstructured.Unstructured, error) {\n\t\t\treturn s.downstream.Update(s.ctx, o, metav1.UpdateOptions{})\n\t\t}\n\tcase !srcExists && dstExists:\n\t\t// Delete dst.\n\t\tif err := s.downstream.Delete(s.ctx, dst.GetName(), metav1.DeleteOptions{}); err != nil {\n\t\t\tif isNotFoundError(err) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn newAPIErrorf(dst, \"downstream delete failed: %s\", err)\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\tslog.Error(\"unhandled condition\",\n\t\t\tslog.Bool(\"srcExists\", srcExists),\n\t\t\tslog.Bool(\"dstExists\", dstExists))\n\t\tos.Exit(1)\n\t\treturn nil\n\t}\n\n\t// Before creating/updating, check if deletion is in progress. This\n\t// is checked separately to src/dstExists for readability (hopefully).\n\tif src.GetDeletionTimestamp() != nil {\n\t\tif err := s.downstream.Delete(s.ctx, src.GetName(), metav1.DeleteOptions{}); err != nil {\n\t\t\tif isNotFoundError(err) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn newAPIErrorf(dst, \"downstream delete failed: %s\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Create/update dst with the labels+annotations+spec of src.\n\tdst.SetLabels(src.GetLabels())\n\tdst.SetAnnotations(src.GetAnnotations())\n\tdst.Object[\"spec\"] = src.Object[\"spec\"]\n\n\t// The remote-resource-version annotation is removed from dst to\n\t// prevent an infinite loop, because changing the annotation would\n\t// change the resource version.\n\tdeleteAnnotation(dst, annotationResourceVersion)\n\n\tif _, err = createOrUpdate(dst); err != nil {\n\t\t// Count subsequent conflict errors\n\t\tif k8serrors.IsConflict(err) && s.clusterName == cloudClusterName {\n\t\t\ts.conflictErrors += 1\n\t\t}\n\t\treturn newAPIErrorf(dst, \"failed to create or update downstream: %s\", err)\n\t}\n\t// Reset error count\n\tif s.clusterName == cloudClusterName {\n\t\ts.conflictErrors = 0\n\t}\n\treturn nil\n}\n\nfunc isNotFoundError(err error) bool {\n\tstatus, ok := err.(*k8serrors.StatusError)\n\treturn ok && status.ErrStatus.Code == http.StatusNotFound\n}\n\ntype apiError struct {\n\to   *unstructured.Unstructured\n\tmsg string\n}\n\nfunc (e apiError) Error() string {\n\treturn fmt.Sprintf(\"%s %s/%s @ %s: %s\", e.o.GetKind(), e.o.GetNamespace(), e.o.GetName(), e.o.GetResourceVersion(), e.msg)\n}\n\nfunc newAPIErrorf(o *unstructured.Unstructured, format string, args ...interface{}) apiError {\n\treturn apiError{o: o, msg: fmt.Sprintf(format, args...)}\n}\n\n// keyFunc extracts a key of the form [<namespace>/]<name> from a resource\n// which is used to access the informer's store and index.\nfunc keyFunc(obj interface{}) (string, bool) {\n\tk, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)\n\tif err != nil {\n\t\tslog.Warn(\"deriving key failed\", ilog.Err(err))\n\t\treturn k, false\n\t}\n\treturn k, true\n}\n\nfunc setAnnotation(o *unstructured.Unstructured, key, value string) {\n\tannotations := o.GetAnnotations()\n\tif annotations == nil {\n\t\tannotations = make(map[string]string)\n\t}\n\tannotations[key] = value\n\to.SetAnnotations(annotations)\n}\n\nfunc deleteAnnotation(o *unstructured.Unstructured, key string) {\n\tannotations := o.GetAnnotations()\n\tif annotations != nil {\n\t\tdelete(annotations, key)\n\t}\n\tif len(annotations) > 0 {\n\t\to.SetAnnotations(annotations)\n\t} else {\n\t\to.SetAnnotations(nil)\n\t}\n\n}\n\nfunc copyStatus(dst, src *unstructured.Unstructured) {\n\tdst.Object[\"status\"] = src.DeepCopy().Object[\"status\"]\n\t// If this CR uses the observedGeneration convention, ensure that we\n\t// preserve the **equality** between generation and observedGeneration,\n\t// since the generations themselves will differ between local and remote.\n\tsrcStatus, ok := src.Object[\"status\"].(map[string]interface{})\n\tif !ok {\n\t\t// Status is not a dict => no observedGeneration.\n\t\treturn\n\t}\n\tdstStatus := dst.Object[\"status\"].(map[string]interface{})\n\tif srcOG, ok := srcStatus[\"observedGeneration\"].(int64); ok {\n\t\tif src.GetGeneration() == srcOG {\n\t\t\tdstStatus[\"observedGeneration\"] = dst.GetGeneration()\n\t\t} else {\n\t\t\t// The controller of this CR has not observed the latest generation.\n\t\t\tdstStatus[\"observedGeneration\"] = 0\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer/syncer_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\tcrdtypes \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tk8sfake \"k8s.io/client-go/dynamic/fake\"\n\tk8stest \"k8s.io/client-go/testing\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\n// filterReadActions drops read-only actions that we don't care about to verify\n// the correct behavior.\nfunc filterReadActions(actions []k8stest.Action) (ret []k8stest.Action) {\n\tfor _, a := range actions {\n\t\tif v := a.GetVerb(); v == \"watch\" || v == \"list\" || v == \"get\" {\n\t\t\tcontinue\n\t\t}\n\t\tret = append(ret, a)\n\t}\n\treturn ret\n}\n\ntype fixture struct {\n\t*testing.T\n\n\tlocal  *k8sfake.FakeDynamicClient\n\tremote *k8sfake.FakeDynamicClient\n\n\t// Starting state the respective client will report.\n\tremoteObjects []runtime.Object\n\tlocalObjects  []runtime.Object\n\t// Actions we want to see called against the respective client.\n\tremoteActions []k8stest.Action\n\tlocalActions  []k8stest.Action\n}\n\nfunc newFixture(t *testing.T) *fixture {\n\treturn &fixture{T: t}\n}\n\nfunc (f *fixture) newCRSyncer(crd crdtypes.CustomResourceDefinition, robotName string) (*crSyncer, schema.GroupVersionResource) {\n\tgvk := schema.GroupVersionKind{\n\t\tGroup:   crd.Spec.Group,\n\t\tVersion: crd.Spec.Versions[0].Name,\n\t\tKind:    crd.Spec.Names.Kind,\n\t}\n\ts := runtime.NewScheme()\n\ts.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})\n\n\tf.local = k8sfake.NewSimpleDynamicClientWithCustomListKinds(s,\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\t{\n\t\t\t\tGroup:    crd.Spec.Group,\n\t\t\t\tVersion:  crd.Spec.Versions[0].Name,\n\t\t\t\tResource: crd.Spec.Names.Plural,\n\t\t\t}: fmt.Sprintf(\"%sList\", gvk.Kind),\n\t\t},\n\t\tf.localObjects...,\n\t)\n\tf.remote = k8sfake.NewSimpleDynamicClientWithCustomListKinds(s,\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\t{\n\t\t\t\tGroup:    crd.Spec.Group,\n\t\t\t\tVersion:  crd.Spec.Versions[0].Name,\n\t\t\t\tResource: crd.Spec.Names.Plural,\n\t\t\t}: fmt.Sprintf(\"%sList\", gvk.Kind),\n\t\t},\n\t\tf.remoteObjects...,\n\t)\n\n\tcrs, err := newCRSyncer(context.Background(), crd, f.local, f.remote, robotName)\n\tif err != nil {\n\t\tf.Fatal(err)\n\t}\n\treturn crs, schema.GroupVersionResource{\n\t\tGroup:    crd.Spec.Group,\n\t\tVersion:  crd.Spec.Versions[0].Name,\n\t\tResource: crd.Spec.Names.Plural,\n\t}\n}\n\nfunc (f *fixture) addLocalObjects(objs ...runtime.Object) {\n\tf.localObjects = append(f.localObjects, objs...)\n}\n\nfunc (f *fixture) addRemoteObjects(objs ...runtime.Object) {\n\tf.remoteObjects = append(f.remoteObjects, objs...)\n}\n\nfunc (f *fixture) expectLocalActions(as ...k8stest.Action) {\n\tf.localActions = append(f.localActions, as...)\n}\n\nfunc (f *fixture) expectRemoteActions(as ...k8stest.Action) {\n\tf.remoteActions = append(f.remoteActions, as...)\n}\n\nfunc (f *fixture) verifyWriteActions() {\n\tvar (\n\t\tlocalWrites  = filterReadActions(f.local.Actions())\n\t\tremoteWrites = filterReadActions(f.remote.Actions())\n\t)\n\tif diff := cmp.Diff(localWrites, f.localActions); diff != \"\" {\n\t\tf.Errorf(\"local writes did not match (-want +got):\\n%s\", diff)\n\t}\n\tif diff := cmp.Diff(remoteWrites, f.remoteActions); diff != \"\" {\n\t\tf.Errorf(\"remote writes did not match (-want +got):\\n%s\", diff)\n\t}\n}\n\n// testCRD returns a basic resource definition we use for custom testing.\n// It may be altered for specific tests.\n// By default it has set the spec-source annotation to \"cloud\".\nfunc testCRD(scope crdtypes.ResourceScope) crdtypes.CustomResourceDefinition {\n\treturn crdtypes.CustomResourceDefinition{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:   \"goals.crds.example.com\",\n\t\t\tLabels: map[string]string{},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tannotationSpecSource: \"cloud\",\n\t\t\t},\n\t\t},\n\t\tSpec: crdtypes.CustomResourceDefinitionSpec{\n\t\t\tGroup: \"crds.example.com\",\n\t\t\tNames: crdtypes.CustomResourceDefinitionNames{\n\t\t\t\tKind:     \"Goal\",\n\t\t\t\tSingular: \"goal\",\n\t\t\t\tPlural:   \"goals\",\n\t\t\t},\n\t\t\tScope: scope,\n\t\t\tVersions: []crdtypes.CustomResourceDefinitionVersion{{\n\t\t\t\tName:    \"v1\",\n\t\t\t\tServed:  true,\n\t\t\t\tStorage: true,\n\t\t\t}},\n\t\t},\n\t}\n}\n\n// newTestCR creates a new custom resource that matches the definition of testCRD(\"Namespaced\").\nfunc newTestCR(name string, spec, status interface{}) *unstructured.Unstructured {\n\to := &unstructured.Unstructured{}\n\n\to.SetKind(\"Goal\")\n\to.SetAPIVersion(\"crds.example.com/v1\")\n\to.SetNamespace(metav1.NamespaceDefault)\n\to.SetName(name)\n\n\to.Object[\"spec\"] = spec\n\to.Object[\"status\"] = status\n\n\treturn o\n}\n\n// newClusterScopedTestCR creates a new custom resource that matches the definition of testCRD(\"Cluster\").\nfunc newClusterScopedTestCR(name string, spec, status interface{}) *unstructured.Unstructured {\n\to := &unstructured.Unstructured{}\n\n\to.SetKind(\"Goal\")\n\to.SetAPIVersion(\"crds.example.com/v1\")\n\to.SetName(name)\n\n\to.Object[\"spec\"] = spec\n\to.Object[\"status\"] = status\n\n\treturn o\n}\n\nfunc TestSyncUpstream_createSpec(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\t// When an upstream resource is seen for the first time, it should be\n\t// created in the downstream cluster including its current status.\n\ttcrRemote := newTestCR(\"resource1\", \"spec1\", \"status1\")\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"cluster1\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncUpstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar (\n\t\ttcrLocalNew = newTestCR(\"resource1\", \"spec1\", \"status1\")\n\t)\n\n\tf.expectLocalActions(k8stest.NewCreateAction(gvr, \"default\", tcrLocalNew))\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncClusterScopedCRUpstream_createSpec(t *testing.T) {\n\tcrd := testCRD(crdtypes.ClusterScoped)\n\tf := newFixture(t)\n\n\t// When an upstream resource is seen for the first time, it should be\n\t// created in the downstream cluster including its current status.\n\ttcrRemote := newClusterScopedTestCR(\"resource1\", \"spec1\", \"status1\")\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"cluster1\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncUpstream(\"resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar (\n\t\ttcrLocalNew = newClusterScopedTestCR(\"resource1\", \"spec1\", \"status1\")\n\t)\n\n\tf.expectLocalActions(k8stest.NewCreateAction(gvr, \"\", tcrLocalNew))\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncUpstream_updateSpec(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\t// On upstream update, the spec in the downstream cluster should be adjusted.\n\tvar (\n\t\ttcrLocal  = newTestCR(\"resource1\", \"spec1\", \"status2\")\n\t\ttcrRemote = newTestCR(\"resource1\", \"spec2\", \"status1\")\n\t)\n\tf.addLocalObjects(tcrLocal)\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"cluster1\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncUpstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar (\n\t\ttcrLocalNew = newTestCR(\"resource1\", \"spec2\", \"status2\")\n\t)\n\n\tf.expectLocalActions(k8stest.NewUpdateAction(gvr, \"default\", tcrLocalNew))\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncUpstream_propagateDelete(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\tvar (\n\t\tnow       = metav1.Now()\n\t\ttcrLocal  = newTestCR(\"resource1\", \"spec1\", \"status1\")\n\t\ttcrRemote = newTestCR(\"resource1\", \"spec1\", \"status1\")\n\t)\n\ttcrRemote.SetDeletionTimestamp(&now)\n\n\tf.addLocalObjects(tcrLocal)\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"cluster1\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncUpstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tf.expectLocalActions(\n\t\tk8stest.NewDeleteAction(gvr, \"default\", \"resource1\"),\n\t)\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncDownstream_deleteOrphan(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\t// We have a local resource that has no matching resource in the upstream cluster.\n\t// Trying to sync it again should delete the local copy.\n\ttcrLocal := newTestCR(\"resource1\", \"spec1\", \"status1\")\n\n\tf.addLocalObjects(tcrLocal)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"cluster1\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncDownstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tf.expectLocalActions(\n\t\tk8stest.NewDeleteAction(gvr, \"default\", \"resource1\"),\n\t)\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncDownstream_statusFull(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\tvar (\n\t\ttcrLocal  = newTestCR(\"resource1\", \"spec1\", \"status2\")\n\t\ttcrRemote = newTestCR(\"resource1\", \"spec1\", \"status1\")\n\t)\n\ttcrLocal.SetResourceVersion(\"123\")\n\n\tf.addLocalObjects(tcrLocal)\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncDownstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttcrRemoteNew := newTestCR(\"resource1\", \"spec1\", \"status2\")\n\ttcrRemoteNew.SetAnnotations(map[string]string{\n\t\tannotationResourceVersion: \"123\",\n\t})\n\n\tf.expectRemoteActions(k8stest.NewUpdateAction(gvr, \"default\", tcrRemoteNew))\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncDownstream_statusWithObservedGeneration(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\t// If (and only if) generation==observedGeneration for the local resource,\n\t// the cr-syncer should adjust observedGeneration to match for the remote\n\t// resource:\n\t// -                local: generation = 3, observedGeneration = 3\n\t// - remote (before test): generation = 2, observedGeneration = 1\n\t// - remote  (after test): generation = 2, observedGeneration = 2\n\ttcrLocal := newTestCR(\"resource1\", \"spec1\", map[string]any{\"observedGeneration\": int64(3)})\n\ttcrRemote := newTestCR(\"resource1\", \"spec1\", map[string]any{\"observedGeneration\": int64(1)})\n\ttcrLocal.SetResourceVersion(\"123\")\n\ttcrLocal.SetGeneration(3)\n\ttcrRemote.SetGeneration(2)\n\n\tf.addLocalObjects(tcrLocal)\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\tif err := crs.syncDownstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Expect that tcrRemoteNew's observedGeneration is changed to match its\n\t// generation.\n\ttcrRemoteNew := newTestCR(\"resource1\", \"spec1\", map[string]any{\"observedGeneration\": int64(2)})\n\ttcrRemoteNew.SetGeneration(2)\n\ttcrRemoteNew.SetAnnotations(map[string]string{\n\t\tannotationResourceVersion: \"123\",\n\t})\n\n\tf.expectRemoteActions(k8stest.NewUpdateAction(gvr, \"default\", tcrRemoteNew))\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncDownstream_statusSubtree(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\tvar (\n\t\ttcrLocal = newTestCR(\"resource1\", \"spec1\", map[string]interface{}{\n\t\t\t\"cloud\": \"cloud_1\",\n\t\t\t\"robot\": \"robot_2\",\n\t\t})\n\t\ttcrRemote = newTestCR(\"resource1\", \"spec1\", map[string]interface{}{\n\t\t\t\"cloud\": \"cloud_2\",\n\t\t\t\"robot\": \"robot_1\",\n\t\t})\n\t)\n\ttcrLocal.SetResourceVersion(\"123\")\n\n\tf.addLocalObjects(tcrLocal)\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, gvr := f.newCRSyncer(crd, \"\")\n\tdefer crs.stop()\n\n\tcrs.subtree = \"robot\"\n\tcrs.startInformers()\n\tif err := crs.syncDownstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttcrRemoteNew := newTestCR(\"resource1\", \"spec1\", map[string]interface{}{\n\t\t\"cloud\": \"cloud_2\",\n\t\t\"robot\": \"robot_2\",\n\t})\n\ttcrRemoteNew.SetAnnotations(map[string]string{\n\t\tannotationResourceVersion: \"123\",\n\t})\n\n\tf.expectRemoteActions(k8stest.NewUpdateAction(gvr, \"default\", tcrRemoteNew))\n\tf.verifyWriteActions()\n}\n\nfunc TestSyncDownstream_downstreamNotFound(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\t// If the downstream resource is not present when synced, the upstream\n\t// resource should be added to the upstream queue, so that syncUpstream\n\t// can recreate the downstream resource.  This tests the case where the\n\t// upstream resource was deleted and immediately recreated.\n\tvar (\n\t\ttcrRemote = newTestCR(\"resource1\", \"spec1\", \"status1\")\n\t)\n\n\tf.addRemoteObjects(tcrRemote)\n\n\tcrs, _ := f.newCRSyncer(crd, \"cluster1\")\n\tdefer crs.stop()\n\n\tcrs.startInformers()\n\t// startInformers adds the initial state to the upstream queue.  Ignore\n\t// it, so that we can check that the same resource is requeued.\n\tupstreamChannel := channelFromQueue(t, crs.upstreamQueue, crs.upstreamInf)\n\tselect {\n\tcase <-upstreamChannel:\n\t\t// Ignore.\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"upstream resource was not queued by informer; want %v\", tcrRemote)\n\t}\n\n\tif err := crs.syncDownstream(\"default/resource1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// syncDownstream should have requeued the upstream resource.\n\tselect {\n\tcase got := <-upstreamChannel:\n\t\tif !reflect.DeepEqual(got, tcrRemote) {\n\t\t\tt.Errorf(\"upstream queue got %v; want %v\", got, tcrRemote)\n\t\t}\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"upstream resource was not requeued to %p; want %v\", crs.upstreamQueue, tcrRemote)\n\t}\n\n\t// We don't need to call syncUpstream here, as this is tested by\n\t// TestSyncUpstream_createSpec.\n}\n\nfunc TestCRSyncer_populateWorkqueue(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tf := newFixture(t)\n\n\tcr1 := newTestCR(\"cr1\", \"spec1\", \"status1\")\n\tf.addRemoteObjects(cr1)\n\n\tcrs, _ := f.newCRSyncer(crd, \"\")\n\tdefer crs.stop()\n\tcrs.startInformers()\n\n\t// Workqueue exposes no interface to select{} over, so we call Get()\n\t// in a goroutine to surface deadlocks properly.\n\tchannel := make(chan interface{}, 1)\n\tgo func() {\n\t\tkey, quit := crs.upstreamQueue.Get()\n\t\tif quit {\n\t\t\tt.Errorf(\"unexpected quit\")\n\t\t\treturn\n\t\t}\n\t\titem, exists, err := crs.upstreamInf.GetIndexer().GetByKey(key.(string))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected lookup error for key %s: %s\", key, err)\n\t\t}\n\t\tif !exists {\n\t\t\tt.Errorf(\"item for key %s does not exist\", key)\n\t\t} else {\n\t\t\tchannel <- item\n\t\t}\n\t}()\n\n\tselect {\n\tcase obj := <-channel:\n\t\tif got := obj.(*unstructured.Unstructured); !reflect.DeepEqual(got, cr1) {\n\t\t\tt.Errorf(\"unexpected object; want %v; got %v\", cr1, got)\n\t\t}\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"Received no watch event; wanted %v\", cr1)\n\t}\n}\n\nfunc TestCRSyncer_populateWorkqueueWithFilter(t *testing.T) {\n\tcrd := testCRD(crdtypes.NamespaceScoped)\n\tcrd.ObjectMeta.Annotations[annotationFilterByRobotName] = \"true\"\n\n\tf := newFixture(t)\n\n\t// Create three CRs of which only one matches the robot the CR syncer\n\t// is running on.\n\tcrCorrectRobot := newTestCR(\"cr1\", \"spec1\", \"status1\")\n\tcrWrongRobot := newTestCR(\"cr2\", \"spec2\", \"status2\")\n\tcrNoRobot := newTestCR(\"cr3\", \"spec3\", \"status3\")\n\n\tcrCorrectRobot.SetLabels(map[string]string{labelRobotName: \"robot-1\"})\n\tcrWrongRobot.SetLabels(map[string]string{labelRobotName: \"robot-2\"})\n\n\tf.addRemoteObjects(crCorrectRobot, crWrongRobot, crNoRobot)\n\n\tcrs, _ := f.newCRSyncer(crd, \"robot-1\")\n\tdefer crs.stop()\n\tcrs.startInformers()\n\n\tchannel := channelFromQueue(t, crs.upstreamQueue, crs.upstreamInf)\n\tselect {\n\tcase got := <-channel:\n\t\tif !reflect.DeepEqual(got, crCorrectRobot) {\n\t\t\tt.Errorf(\"unexpected object; want %v; got %v\", crCorrectRobot, got)\n\t\t}\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"Received no watch event; wanted %v\", crCorrectRobot)\n\t}\n\t// No other items should come through.\n\tselect {\n\tcase item := <-channel:\n\t\tt.Errorf(\"Unexpected update: %v\", item)\n\tcase <-time.After(3 * time.Second):\n\t}\n}\n\nfunc channelFromQueue(t *testing.T, queue workqueue.Interface, inf cache.SharedIndexInformer) <-chan *unstructured.Unstructured {\n\tch := make(chan *unstructured.Unstructured, 1)\n\tgo func() {\n\t\tdefer close(ch)\n\t\tfor {\n\t\t\tkey, quit := queue.Get()\n\t\t\tif quit {\n\t\t\t\tt.Errorf(\"unexpected quit\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\titem, exists, err := inf.GetIndexer().GetByKey(key.(string))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected lookup error for key %s: %s\", key, err)\n\t\t\t}\n\t\t\tif !exists {\n\t\t\t\tt.Errorf(\"item for key %s does not exist\", key)\n\t\t\t} else {\n\t\t\t\tch <- item.(*unstructured.Unstructured)\n\t\t\t\tqueue.Done(key)\n\t\t\t}\n\t\t}\n\t}()\n\treturn ch\n\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer-auth-webhook/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\", \"go_test\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"main.go\",\n        \"request.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/cr-syncer-auth-webhook\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@org_golang_x_oauth2//jws:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"cr-syncer-auth-webhook-app\",\n    embed = [\":go_default_library\"],\n)\n\npkg_tar(\n    name = \"cr-syncer-auth-webhook-layer\",\n    srcs = [\":cr-syncer-auth-webhook-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"cr-syncer-auth-webhook-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/cr-syncer-auth-webhook-app\"],\n    tars = [\":cr-syncer-auth-webhook-layer\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"request_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\"@com_github_google_go_cmp//cmp:go_default_library\"],\n)\n"
  },
  {
    "path": "src/go/cmd/cr-syncer-auth-webhook/main.go",
    "content": "// Copyright 2025 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// The cr-syncer-auth-webhook verifies that requests from the cr-syncer are\n// limited to the robot named in the credentials.\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/oauth2/jws\"\n\n\t\"github.com/googlecloudrobotics/ilog\"\n)\n\nvar (\n\tport = flag.Int(\"port\", 8080,\n\t\t\"Listening port for HTTP requests\")\n\n\tacceptLegacyCredentials = flag.Bool(\"accept-legacy-service-account-credentials\", false,\n\t\t\"Whether to accept legacy GCP service account credentials\")\n\n\ttokenVendor = flag.String(\"token-vendor\", \"http://token-vendor.app-token-vendor.svc.cluster.local\",\n\t\t\"Hostname of the token-vendor service\")\n\n\tlogLevel = flag.Int(\"log-level\", int(slog.LevelInfo),\n\t\t\"the log message level required to be logged\")\n)\n\nconst (\n\tverifyJWTEndpoint = \"/apis/core.token-vendor/v1/jwt.verify\"\n\n\tlegacyTokenPrefix = \"ya29.\"\n)\n\ntype handlers struct {\n\tclient *http.Client\n}\n\nfunc newHandlers() handlers {\n\treturn handlers{\n\t\tclient: &http.Client{},\n\t}\n}\n\nfunc (h *handlers) health(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(http.StatusOK)\n}\n\n// verifyJWT delegates to the token-vendor to verify the signature of the JWT\n// matches the public key of the robot.\nfunc (h *handlers) verifyJWT(encodedJWT string) error {\n\tif strings.HasPrefix(encodedJWT, legacyTokenPrefix) {\n\t\t// We can avoid the unnecessary request when the client is using a GCP\n\t\t// access token.\n\t\treturn fmt.Errorf(\"legacy token format\")\n\t}\n\n\treq, err := http.NewRequest(\"GET\", *tokenVendor+verifyJWTEndpoint, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create request: %w\", err)\n\t}\n\treq.Header.Add(\"Authorization\", \"Bearer \"+encodedJWT)\n\tresp, err := h.client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"do request: %w\", err)\n\t}\n\t// Discard body so connection can be reused.\n\tio.Copy(io.Discard, resp.Body)\n\tresp.Body.Close()\n\n\tif resp.StatusCode == http.StatusForbidden {\n\t\treturn fmt.Errorf(\"invalid JWT\")\n\t} else if resp.StatusCode != http.StatusOK {\n\t\tslog.Warn(\"unexpected status code from /jwt.verify\", slog.Int(\"Status\", resp.StatusCode))\n\t\treturn fmt.Errorf(\"unexpected status code\")\n\t}\n\treturn nil\n}\n\nfunc (h *handlers) resourceIsFiltered(groupKind string) bool {\n\t// TODO: limit to CRDs with filter-by-robot-name label in case someone adds\n\t// new unfiltered resources in future.\n\treturn groupKind != \"registry.cloudrobotics.com/robottypes\"\n}\n\n// validateRequest checks that the request is expected for the cr-syncer and\n// only accesses allowed resources.\nfunc (h *handlers) validateRequest(r *http.Request, robotName string) error {\n\turlString := r.Header.Get(\"X-Original-Url\")\n\tincomingReq, err := parseURL(urlString)\n\tif err != nil {\n\t\tslog.Error(\"unexpected value of X-Original-Url\", slog.String(\"URL\", urlString), ilog.Err(err))\n\t\treturn err\n\t}\n\n\tif !h.resourceIsFiltered(incomingReq.GroupKind) {\n\t\t// Unfiltered resources (eg robottypes) are always allowed.\n\t\t//\n\t\t// For additional defense-in-depth, we could check if the CRD has\n\t\t// annotations for the cr-syncer. However, the RBAC policy in\n\t\t// cr-syncer-policy.yaml already limits the client to syncable resources.\n\t\treturn nil\n\t}\n\n\t// TODO: check against label of upstream resource instead of assuming that\n\t// robot xyz can access all syncable resources matching *xyz.\n\tif incomingReq.RobotName != robotName && !strings.HasSuffix(incomingReq.ResourceName, robotName) {\n\t\tslog.Error(\"robot impersonation rejected\",\n\t\t\tslog.String(\"SourceName\", robotName),\n\t\t\tslog.String(\"TargetName\", incomingReq.RobotName+incomingReq.ResourceName),\n\t\t\tslog.String(\"Kind\", incomingReq.GroupKind),\n\t\t\tslog.String(\"URL\", urlString),\n\t\t)\n\t\treturn errors.New(\"credentials rejected\")\n\t}\n\treturn nil\n}\n\n// auth is a webhook to inspect incoming requests from the cr-syncer, check if\n// they are allowed, and if so, provide an Authorization header so the K8s\n// apiserver will serve them. This lets nginx handle the request & response\n// bodies itself.\nfunc (h *handlers) auth(w http.ResponseWriter, r *http.Request) {\n\tencodedJWT := strings.TrimPrefix(r.Header.Get(\"Authorization\"), \"Bearer \")\n\tif err := h.verifyJWT(encodedJWT); err != nil {\n\t\tif *acceptLegacyCredentials {\n\t\t\t// The request already has the necessary credentials, so preserve these.\n\t\t\tw.Header().Add(\"Authorization\", r.Header.Get(\"Authorization\"))\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\t}\n\n\t\thttp.Error(w, \"No valid credentials provided\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\t// verifyJWT() has already checked the signature so we don't need to.\n\tclaims, err := jws.Decode(encodedJWT)\n\tif err != nil {\n\t\tslog.Error(\"Failed to parse JWT despite previous verification\")\n\t\thttp.Error(w, \"Credentials could not be parsed\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tslog.Debug(\"JWT parsed\", slog.String(\"ID\", claims.Sub))\n\n\tif err := h.validateRequest(r, claims.Sub); err != nil {\n\t\thttp.Error(w, \"Request not allowed\", http.StatusForbidden)\n\t\treturn\n\t}\n\n\t// Provide a k8s token to nginx so that GKE accepts the request. Policy for\n\t// the cr-syncer-auth-webhook ServiceAccount is defined in\n\t// cr-syncer-policy.yaml.\n\tk8sToken, err := os.ReadFile(\"/var/run/secrets/kubernetes.io/serviceaccount/token\")\n\tif err != nil {\n\t\tslog.Error(\"failed to read /var/run/secrets/kubernetes.io/serviceaccount/token\", ilog.Err(err))\n\t\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\t}\n\tw.Header().Add(\"Authorization\", \"Bearer \"+string(k8sToken))\n\tw.WriteHeader(http.StatusOK)\n}\n\nfunc main() {\n\tflag.Parse()\n\tlogHandler := ilog.NewLogHandler(slog.Level(*logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tserver := &http.Server{\n\t\tAddr: fmt.Sprintf(\":%d\", *port),\n\t}\n\thandlers := newHandlers()\n\thttp.HandleFunc(\"/healthz\", handlers.health)\n\thttp.HandleFunc(\"/auth\", handlers.auth)\n\n\tgo func() {\n\t\tslog.Info(\"Serving requests...\")\n\t\tif err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {\n\t\t\tslog.Error(\"server.ListenAndServe() failed unexpectedly\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tslog.Info(\"Stopped serving new connections.\")\n\t}()\n\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)\n\t<-sigChan\n\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tif err := server.Shutdown(ctx); err != nil {\n\t\tslog.Error(\"server.Shutdown() failed unexpectedly\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tslog.Info(\"Server shutdown complete.\")\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer-auth-webhook/request.go",
    "content": "// request.go contains methods for understanding and validating the incoming\n// request.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Regex for RFC 1123 subdomain format\n// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names\n// https://github.com/kubernetes/kubernetes/blob/976a940f4a4e84fe814583848f97b9aafcdb083f/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go#L209\nvar isValidRobotName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`).MatchString\n\n// the prefix of the label selector query param used by the cr-syncer\nconst robotNameSelectorPrefix = \"cloudrobotics.com/robot-name=\"\n\n// incomingRequest contains the authz-relevant properties of the resource\ntype incomingRequest struct {\n\t// GroupKind, eg \"registry.cloudrobotics.com/robots\"\n\tGroupKind string\n\n\t// RobotName, or empty if no label selector is used (eg for a Get or Update)\n\tRobotName string\n\n\t// ResourceName, or empty if no resource is specified (eg for a List or Watch of a filtered resource)\n\tResourceName string\n}\n\n// parseURL parses the URL that the cr-syncer is hitting to find the\n// authz-relevant properties.\nfunc parseURL(urlString string) (*incomingRequest, error) {\n\tresult := incomingRequest{}\n\turl, err := url.Parse(urlString)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Path should be one of:\n\t//  /apis/core.kubernetes/apis/<group>/<version>/<kind>\n\t//  /apis/core.kubernetes/apis/<group>/<version>/namespaces/<namespace>/<kind>\n\t//  /apis/core.kubernetes/apis/<group>/<version>/namespaces/<namespace>/<kind>/<resourceName>\n\t//  /apis/core.kubernetes/apis/<group>/<version>/namespaces/<namespace>/<kind>/<resourceName>/status\n\t//                             parts[0] parts[1] parts[2]   parts[3]   parts[4] parts[5]\n\tparts := strings.Split(strings.TrimPrefix(url.Path, \"/apis/core.kubernetes/apis/\"), \"/\")\n\tif len(parts) < 3 || len(parts) > 7 {\n\t\treturn nil, errors.New(\"unexpected URL length\")\n\t}\n\tif parts[2] != \"namespaces\" {\n\t\t// Add in \"/namespaces/default\" so remaining code can use fixed indices.\n\t\t// I also considered a regexp but it's not pretty:\n\t\t// \"/apis/core.kubernetes/apis/([^/]*)/([^/]*)(/namespaces/[^/]*)?/([^/]*)/?([^/]*)(/status)?\"\n\t\tparts = slices.Insert(parts, 2, \"namespaces\", \"default\")\n\t}\n\n\tresult.GroupKind = fmt.Sprintf(\"%s/%s\", parts[0], parts[4])\n\tif len(parts) > 5 {\n\t\t// if a resourceName is in the URL, we don't need to look at the query parameters\n\t\tresult.ResourceName = parts[5]\n\t\treturn &result, nil\n\t}\n\n\t// If we have no resourceName, this is a list/watch request, so check for a\n\t// labelSelector.\n\tparams := url.Query()\n\tlabelSelectors := params[\"labelSelector\"]\n\tif len(labelSelectors) == 0 {\n\t\t// This is an unfiltered List or Watch request (eg for robottypes).\n\t\treturn &result, nil\n\t}\n\tif len(labelSelectors) > 1 || !strings.HasPrefix(labelSelectors[0], robotNameSelectorPrefix) {\n\t\treturn nil, errors.New(\"invalid label selector\")\n\t}\n\tresult.RobotName = strings.TrimPrefix(labelSelectors[0], robotNameSelectorPrefix)\n\tif !isValidRobotName(result.RobotName) {\n\t\treturn nil, errors.New(\"invalid robot name\")\n\t}\n\treturn &result, nil\n}\n"
  },
  {
    "path": "src/go/cmd/cr-syncer-auth-webhook/request_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc TestParseURL(t *testing.T) {\n\ttests := []struct {\n\t\tdesc string\n\t\turl  string\n\t\twant incomingRequest\n\t}{\n\t\t{\n\t\t\tdesc: \"watch request, filtered\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/chartassignments?labelSelector=cloudrobotics.com%2Frobot-name%3Dmy-robot\",\n\t\t\twant: incomingRequest{\n\t\t\t\tGroupKind: \"apps.cloudrobotics.com/chartassignments\",\n\t\t\t\tRobotName: \"my-robot\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"watch request, unfiltered\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/registry.cloudrobotics.com/v1alpha1/robottypes\",\n\t\t\twant: incomingRequest{\n\t\t\t\tGroupKind: \"registry.cloudrobotics.com/robottypes\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"watch request, with namespace\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/namespaces/default/chartassignments?labelSelector=cloudrobotics.com%2Frobot-name%3Dmy-robot\",\n\t\t\twant: incomingRequest{\n\t\t\t\tGroupKind: \"apps.cloudrobotics.com/chartassignments\",\n\t\t\t\tRobotName: \"my-robot\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"get request\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/namespaces/default/chartassignments/resource-for-my-robot\",\n\t\t\twant: incomingRequest{\n\t\t\t\tGroupKind:    \"apps.cloudrobotics.com/chartassignments\",\n\t\t\t\tResourceName: \"resource-for-my-robot\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"status post request, with namespace\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/namespaces/default/chartassignments/resource-for-my-robot/status\",\n\t\t\twant: incomingRequest{\n\t\t\t\tGroupKind:    \"apps.cloudrobotics.com/chartassignments\",\n\t\t\t\tResourceName: \"resource-for-my-robot\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"status post request, without namespace\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/chartassignments/resource-for-my-robot/status?timeout=5m5s\",\n\t\t\twant: incomingRequest{\n\t\t\t\tGroupKind:    \"apps.cloudrobotics.com/chartassignments\",\n\t\t\t\tResourceName: \"resource-for-my-robot\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tgot, err := parseURL(tc.url)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"parseURL(%q) returned error: %v\", tc.url, err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tc.want, *got); diff != \"\" {\n\t\t\t\tt.Errorf(\"parseURL(%q) returned diff (-want +got):\\n%s\", tc.url, diff)\n\t\t\t}\n\t\t})\n\t}\n}\nfunc TestParseURLErrors(t *testing.T) {\n\ttests := []struct {\n\t\tdesc string\n\t\turl  string\n\t}{\n\t\t{\n\t\t\tdesc: \"empty robot name\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/chartassignments?labelSelector=cloudrobotics.com%2Frobot-name%3D\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"over-broad label selector: robot-name!=my-robot\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/apis/apps.cloudrobotics.com/v1alpha1/chartassignments?labelSelector=cloudrobotics.com%2Frobot-name%21%3Dmy-robot\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"core API (not a CR)\",\n\t\t\turl:  \"http://host/apis/core.kubernetes/api/v1/namespaces/default/pods/cr-syncer-6676b4958d-p9hqw\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\t_, err := parseURL(tc.url)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"parseURL(%q) succeeded unexpected\", tc.url)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/gcr-credential-refresher/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/gcr-credential-refresher\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/gcr:go_default_library\",\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"gcr-credential-refresher-app\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n)\n\npkg_tar(\n    name = \"gcr-credential-refresher-image-layer\",\n    srcs = [\":gcr-credential-refresher-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"gcr-credential-refresher-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/gcr-credential-refresher-app\"],\n    tars = [\":gcr-credential-refresher-image-layer\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/gcr-credential-refresher/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/gcr\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n)\n\nvar (\n\trobotIdFile = flag.String(\"robot_id_file\", \"\", \"robot-id.json file\")\n\trobotSAName = flag.String(\"service_account\", \"robot-service\", \"Robot default service account name, default: robot-service\")\n)\n\nconst updateInterval = 10 * time.Minute\n\n// Updates the token used to pull images from GCR in the surrounding cluster.\nfunc updateCredentials(ctx context.Context) error {\n\t// Connect to the surrounding k8s cluster.\n\tlocalConfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlocalClient, err := kubernetes.NewForConfig(localConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\trobotAuth, err := robotauth.LoadFromFile(*robotIdFile)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to read robot id file %s: %v\", *robotIdFile, err)\n\t}\n\n\teffectiveSA, err := robotAuth.ServiceAccountEmail(*robotSAName)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to construct service account from '%s': %v\", *robotSAName, err)\n\t}\n\n\t// Perform a token exchange with the TokenVendor in the cloud cluster and update the\n\t// credentials used to pull images from GCR.\n\treturn gcr.UpdateGcrCredentials(ctx, localClient, robotAuth, effectiveSA)\n}\n\n// Updates the token used to pull images from GCR in the surrounding cluster. The update runs\n// on startup, and then every 10 minutes.\nfunc main() {\n\tflag.Parse()\n\tctx := context.Background()\n\n\tfor {\n\t\tif err := updateCredentials(ctx); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Printf(\"Updated GCR credentials in local cluster\")\n\t\ttime.Sleep(updateInterval)\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-client/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-client\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/cmd/http-relay-client/client:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n        \"@io_opencensus_go_contrib_exporter_stackdriver//:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"http-relay-client-app\",\n    embed = [\":go_default_library\"],\n)\n\npkg_tar(\n    name = \"http-relay-client-image-layer\",\n    srcs = [\":http-relay-client-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"http-relay-client-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/http-relay-client-app\"],\n    tars = [\":http-relay-client-image-layer\"],\n)\n"
  },
  {
    "path": "src/go/cmd/http-relay-client/client/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"client.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-client/client\",\n    deps = [\n        \"//src/proto/http-relay:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_opencensus_go//plugin/ochttp:go_default_library\",\n        \"@io_opencensus_go//plugin/ochttp/propagation/tracecontext:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n        \"@org_golang_google_protobuf//proto:go_default_library\",\n        \"@org_golang_x_net//http2:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n        \"@org_golang_x_oauth2//google:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\"client_test.go\"],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/proto/http-relay:go_default_library\",\n        \"@com_github_onsi_gomega//:go_default_library\",\n        \"@in_gopkg_h2non_gock_v1//:go_default_library\",\n        \"@org_golang_google_protobuf//proto:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/http-relay-client/client/client.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package main runs a local HTTP relay client.\n//\n// See the documentation of ../http-relay-server/main.go for details about\n// the system architecture. In a nutshell, this program pulls serialized HTTP\n// requests from a remote relay server, redirects them to a local backend, and\n// posts the serialized response to the relay server.\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\tpb \"github.com/googlecloudrobotics/core/src/proto/http-relay\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\n\t\"github.com/cenkalti/backoff\"\n\t\"go.opencensus.io/plugin/ochttp\"\n\t\"go.opencensus.io/plugin/ochttp/propagation/tracecontext\"\n\t\"go.opencensus.io/trace\"\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar (\n\tErrTimeout        = errors.New(http.StatusText(http.StatusRequestTimeout))\n\tErrForbidden      = errors.New(http.StatusText(http.StatusForbidden))\n\tdebugLogs    bool = false\n)\n\n// This is a package internal variable which we define to be able to overwrite\n// the measured time during unit tests. This is a light weight alternative\n// to mocking the entire time interface and passing it along all call paths.\nvar timeSince = time.Since\n\ntype ClientConfig struct {\n\tRemoteRequestTimeout   time.Duration\n\tBackendResponseTimeout time.Duration\n\tIdleConnTimeout        time.Duration\n\tReadIdleTimeout        time.Duration\n\n\tDisableAuthForRemote    bool\n\tRootCAFile              string\n\tAuthenticationTokenFile string\n\n\tBackendScheme  string\n\tBackendAddress string\n\tBackendPath    string\n\tPreserveHost   bool\n\n\tRelayScheme  string\n\tRelayAddress string\n\tRelayPrefix  string\n\n\tServerName string\n\n\tNumPendingRequests  int\n\tMaxIdleConnsPerHost int\n\n\tMaxChunkSize int\n\tBlockSize    int\n\n\tDisableHttp2 bool\n\tForceHttp2   bool\n}\n\ntype RelayServerError struct {\n\tmsg string\n}\n\nfunc NewRelayServerError(msg string) error {\n\treturn &RelayServerError{msg}\n}\n\nfunc (e *RelayServerError) Error() string {\n\treturn e.msg\n}\n\nfunc DefaultClientConfig() ClientConfig {\n\treturn ClientConfig{\n\t\tRemoteRequestTimeout:   60 * time.Second,\n\t\tBackendResponseTimeout: 100 * time.Millisecond,\n\n\t\t// ReadIdleTimeout works around an upstream issue by enabling\n\t\t// HTTP/2 PING, so we recover faster after the node IP changes.\n\t\t// IdleConnTimeout is here because I was worried this would\n\t\t// create unnecessary load with PINGs on long-idle connections.\n\t\t// https://github.com/golang/go/issues/59690\n\t\tReadIdleTimeout: 30 * time.Second,\n\t\tIdleConnTimeout: 120 * time.Second,\n\n\t\tDisableAuthForRemote:    false,\n\t\tRootCAFile:              \"\",\n\t\tAuthenticationTokenFile: \"\",\n\n\t\tBackendScheme:  \"https\",\n\t\tBackendAddress: \"localhost:8080\",\n\t\tBackendPath:    \"\",\n\t\tPreserveHost:   true,\n\n\t\tRelayScheme:  \"https\",\n\t\tRelayAddress: \"localhost:8081\",\n\t\tRelayPrefix:  \"\",\n\n\t\tServerName: \"server_name\",\n\n\t\tNumPendingRequests:  1,\n\t\tMaxIdleConnsPerHost: 100,\n\n\t\tMaxChunkSize: 50 * 1024,\n\t\tBlockSize:    10 * 1024,\n\n\t\tDisableHttp2: false,\n\t\tForceHttp2:   false,\n\t}\n}\n\ntype Client struct {\n\tconfig ClientConfig\n}\n\nfunc NewClient(config ClientConfig) *Client {\n\tc := &Client{}\n\tc.config = config\n\treturn c\n}\n\nfunc (c *Client) Start() {\n\tvar err error\n\n\tremoteTransport := http.DefaultTransport.(*http.Transport).Clone()\n\tremoteTransport.MaxIdleConns = c.config.MaxIdleConnsPerHost\n\tremoteTransport.MaxIdleConnsPerHost = c.config.MaxIdleConnsPerHost\n\tremoteTransport.IdleConnTimeout = c.config.IdleConnTimeout\n\thttp2Trans, err := http2.ConfigureTransports(remoteTransport)\n\tif err == nil {\n\t\thttp2Trans.ReadIdleTimeout = c.config.ReadIdleTimeout\n\t}\n\tremote := &http.Client{Transport: remoteTransport}\n\n\tif !c.config.DisableAuthForRemote {\n\t\tctx := context.WithValue(context.Background(), oauth2.HTTPClient, remote)\n\t\tscope := \"https://www.googleapis.com/auth/cloud-platform.read-only\"\n\t\tif remote, err = google.DefaultClient(ctx, scope); err != nil {\n\t\t\tslog.Error(\"unable to set up credentials for relay-server authentication\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tremote.Timeout = c.config.RemoteRequestTimeout\n\n\tvar tlsConfig *tls.Config\n\tif c.config.RootCAFile != \"\" {\n\t\trootCAs := x509.NewCertPool()\n\t\tcerts, err := os.ReadFile(c.config.RootCAFile)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to read CA file\", slog.String(\"File\", c.config.RootCAFile), ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif ok := rootCAs.AppendCertsFromPEM(certs); !ok {\n\t\t\tslog.Error(\"No certs found\", slog.String(\"File\", c.config.RootCAFile))\n\t\t\tos.Exit(1)\n\t\t}\n\t\ttlsConfig = &tls.Config{RootCAs: rootCAs}\n\n\t\tif keyLogFile := os.Getenv(\"SSLKEYLOGFILE\"); keyLogFile != \"\" {\n\t\t\tkeyLog, err := os.OpenFile(keyLogFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)\n\t\t\tif err != nil {\n\t\t\t\tslog.Warn(\"Cannot open keylog file (check SSLKEYLOGFILE env var)\", slog.String(\"File\", keyLogFile), ilog.Err(err))\n\t\t\t} else {\n\t\t\t\ttlsConfig.KeyLogWriter = keyLog\n\t\t\t}\n\t\t}\n\t}\n\n\tvar transport http.RoundTripper\n\tif c.config.ForceHttp2 {\n\t\th2transport := &http2.Transport{}\n\t\th2transport.TLSClientConfig = tlsConfig\n\n\t\tif c.config.DisableHttp2 {\n\t\t\tslog.Error(\"Cannot use --force_http2 together with --disable_http2\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tif c.config.BackendScheme == \"http\" {\n\t\t\t// Enable HTTP/2 Cleartext (H2C) for gRPC backends.\n\t\t\th2transport.AllowHTTP = true\n\t\t\th2transport.DialTLS = func(network, addr string, cfg *tls.Config) (net.Conn, error) {\n\t\t\t\t// Pretend we are dialing a TLS endpoint.\n\t\t\t\t// Note, we ignore the passed tls.Config\n\t\t\t\treturn net.Dial(network, addr)\n\t\t\t}\n\t\t}\n\n\t\ttransport = h2transport\n\t} else {\n\t\th1transport := http.DefaultTransport.(*http.Transport).Clone()\n\t\th1transport.MaxIdleConns = c.config.MaxIdleConnsPerHost\n\t\th1transport.MaxIdleConnsPerHost = c.config.MaxIdleConnsPerHost\n\t\th1transport.TLSClientConfig = tlsConfig\n\n\t\tif c.config.DisableHttp2 {\n\t\t\t// Fix for: http2: invalid Upgrade request header: [\"SPDY/3.1\"]\n\t\t\t// according to the docs:\n\t\t\t//    Programs that must disable HTTP/2 can do so by setting Transport.TLSNextProto (for clients) or\n\t\t\t//    Server.TLSNextProto (for servers) to a non-nil, empty map.\n\t\t\t//\n\t\t\th1transport.TLSNextProto = map[string]func(authority string, c *tls.Conn) http.RoundTripper{}\n\t\t}\n\n\t\ttransport = h1transport\n\t}\n\n\t// TODO(https://github.com/golang/go/issues/31391): reimplement timeouts if possible\n\t// (see also https://github.com/golang/go/issues/30876)\n\tlocal := &http.Client{\n\t\tCheckRedirect: func(*http.Request, []*http.Request) error {\n\t\t\t// Don't follow redirects: instead, pass them through the relay untouched.\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t\tTransport: &ochttp.Transport{Base: transport},\n\t}\n\n\twg := new(sync.WaitGroup)\n\twg.Add(c.config.NumPendingRequests)\n\tfor i := 0; i < c.config.NumPendingRequests; i++ {\n\t\tgo c.localProxyWorker(remote, local)\n\t}\n\t// Waiting for all goroutines to finish (they never do)\n\twg.Wait()\n}\n\nfunc addServiceName(span *trace.Span) {\n\trelayClientAttr := trace.StringAttribute(\"service.name\", \"http-relay-client\")\n\tspan.AddAttributes(relayClientAttr)\n}\n\nfunc (c *Client) getRequest(remote *http.Client, relayURL string) (*pb.HttpRequest, error) {\n\tif debugLogs {\n\t\tslog.Info(\"Connecting to relay server to get next request\", slog.String(\"ServerName\", c.config.ServerName))\n\t}\n\n\tresp, err := remote.Get(relayURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resp.StatusCode == http.StatusRequestTimeout {\n\t\treturn nil, ErrTimeout\n\t}\n\tif resp.StatusCode == http.StatusForbidden {\n\t\treturn nil, ErrForbidden\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"server status %s: %s\", http.StatusText(resp.StatusCode), string(body))\n\t}\n\tbreq := pb.HttpRequest{}\n\terr = proto.Unmarshal(body, &breq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal request: %v. request was: %q\", err, string(body))\n\t}\n\n\treturn &breq, nil\n}\n\nfunc marshalHeader(h *http.Header) []*pb.HttpHeader {\n\tr := []*pb.HttpHeader{}\n\tfor k, vs := range *h {\n\t\tfor _, v := range vs {\n\t\t\tr = append(r, &pb.HttpHeader{Name: proto.String(k), Value: proto.String(v)})\n\t\t}\n\t}\n\treturn r\n}\n\nfunc extractRequestHeader(breq *pb.HttpRequest, header *http.Header) {\n\tfor _, h := range breq.Header {\n\t\theader.Add(*h.Name, *h.Value)\n\t}\n}\n\nfunc (c *Client) createBackendRequest(breq *pb.HttpRequest) (*http.Request, error) {\n\tid := *breq.Id\n\ttargetUrl, err := url.Parse(*breq.Url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttargetUrl.Scheme = c.config.BackendScheme\n\ttargetUrl.Host = c.config.BackendAddress\n\ttargetUrl.Path = c.config.BackendPath + targetUrl.Path\n\tslog.Debug(\"Sending request to backend\",\n\t\tslog.String(\"ID\", id),\n\t\tslog.String(\"Method\", *breq.Method),\n\t\tslog.Any(\"TargetURL\", *targetUrl))\n\treq, err := http.NewRequest(*breq.Method, targetUrl.String(), bytes.NewReader(breq.Body))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif c.config.PreserveHost && breq.Host != nil {\n\t\treq.Host = *breq.Host\n\t}\n\textractRequestHeader(breq, &req.Header)\n\tif c.config.AuthenticationTokenFile != \"\" {\n\t\ttoken, err := os.ReadFile(c.config.AuthenticationTokenFile)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Failed to read authentication token from %s: %v\", c.config.AuthenticationTokenFile, err)\n\t\t}\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\t}\n\n\tif debugLogs {\n\t\tdump, _ := httputil.DumpRequest(req, false)\n\t\tslog.Info(\"DumpRequest\", slog.String(\"Request\", string(dump)))\n\t}\n\n\treturn req, nil\n}\n\n// This function builds and executes a http.Request from the proto request we\n// received from the user-client. This user-client (e.g. Chrome) request is\n// executed in the network in which the relay-client is running. In case of\n// our on-prem cluster, these requests are processed by Istio and sent to the\n// relevant in-cluster service.\n// It returns both a new pb.HttpResponse as well as the related http.Response so\n// that the caller can access e.g. http trailers once the response body has\n// been read.\nfunc makeBackendRequest(ctx context.Context, local *http.Client, req *http.Request, id string) (*pb.HttpResponse, *http.Response, error) {\n\t_, backendSpan := trace.StartSpan(ctx, \"Sent.\"+req.URL.Path)\n\taddServiceName(backendSpan)\n\tf := &tracecontext.HTTPFormat{}\n\tf.SpanContextToRequest(backendSpan.SpanContext(), req)\n\tresp, err := local.Do(req)\n\tif err != nil {\n\t\tbackendSpan.End()\n\t\treturn nil, nil, err\n\t}\n\tbackendSpan.End()\n\n\t_, backendResp := trace.StartSpan(ctx, \"Creating response (proto marshaling)\")\n\taddServiceName(backendResp)\n\tdefer backendResp.End()\n\n\tif debugLogs {\n\t\tslog.Info(\"Backend responded\", slog.String(\"ID\", id), slog.Int(\"Status\", resp.StatusCode))\n\n\t\tdump, _ := httputil.DumpResponse(resp, false)\n\t\tslog.Info(\"DumpResponse\", slog.String(\"Response\", string(dump)))\n\t\t// We get 'Grpc-Status' and 'Grpc-Message' headers that we need to persist.\n\t\t// Why is it not part of Trailers?\n\t\tslog.Info(\"Headers\",\n\t\t\tslog.String(\"ID\", id),\n\t\t\tslog.String(\"Header\", fmt.Sprintf(\"%+v\", resp.Header)))\n\t\t// Initially only keys, values are set after body has be read (EOF)\n\t\tslog.Info(\"Trailers\",\n\t\t\tslog.String(\"ID\", id),\n\t\t\tslog.String(\"Trailer\", fmt.Sprintf(\"%+v\", resp.Trailer)))\n\t}\n\n\treturn &pb.HttpResponse{\n\t\tId:         proto.String(id),\n\t\tStatusCode: proto.Int32(int32(resp.StatusCode)),\n\t\tHeader:     marshalHeader(&resp.Header),\n\t\tTrailer:    marshalHeader(&resp.Trailer),\n\t}, resp, nil\n}\n\nfunc (c *Client) postResponse(remote *http.Client, br *pb.HttpResponse) error {\n\tbody, err := proto.Marshal(br)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresponseUrl := url.URL{\n\t\tScheme: c.config.RelayScheme,\n\t\tHost:   c.config.RelayAddress,\n\t\tPath:   c.config.RelayPrefix + \"/server/response\",\n\t}\n\n\tresp, err := remote.Post(responseUrl.String(), \"application/vnd.google.protobuf;proto=cloudrobotics.http_relay.v1alpha1.HttpResponse\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"couldn't post response to relay server: %v\", err)\n\t}\n\n\tdefer resp.Body.Close()\n\tbody, err = io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"couldn't read relay server's response body: %v\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\terr := NewRelayServerError(fmt.Sprintf(\"relay server responded %s: %s\", http.StatusText(resp.StatusCode), body))\n\t\tif resp.StatusCode == http.StatusBadRequest {\n\t\t\t// http-relay-server may have restarted or the client cancelled the request.\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\treturn err\n\t}\n\t// body is only 2 bytes 'ok'\n\treturn nil\n}\n\n// streamBytes converts an io.Reader into a channel to enable select{}-style timeouts.\nfunc (c *Client) streamBytes(id string, in io.ReadCloser, out chan<- []byte) {\n\teof := false\n\tfor !eof {\n\t\t// This must be a new buffer each time, as the channel is not making a copy\n\t\tbuffer := make([]byte, c.config.BlockSize)\n\t\tif debugLogs {\n\t\t\tslog.Info(\"Reading from backend\", slog.String(\"ID\", id))\n\t\t}\n\t\tn, err := in.Read(buffer)\n\t\tif err != nil && err != io.EOF {\n\t\t\tslog.Error(\"Failed to read from backend\", slog.String(\"ID\", id), ilog.Err(err))\n\t\t}\n\t\teof = err != nil\n\t\tif n > 0 {\n\t\t\tif debugLogs {\n\t\t\t\tslog.Info(\"Forward from backend\", slog.String(\"ID\", id), slog.Int(\"ByteCount\", n))\n\t\t\t}\n\t\t\tout <- buffer[:n]\n\t\t}\n\t}\n\tif debugLogs {\n\t\tslog.Info(\"Got EOF reading from backend\", slog.String(\"ID\", id))\n\t}\n\tclose(out)\n}\n\n// buildResponses collates the bytes from the in stream into HttpResponse objects.\n// This function needs to consider three cases:\n//   - Data is coming fast. We chunk the data into 'maxChunkSize' blocks and keep sending it.\n//   - Data is trickling slow. We accumulate data for the timeout duration and then send it.\n//     Timeout is determined by the maximum latency the user should see.\n//   - No data needs to be transferred. We keep sending empty responses every few seconds\n//     to show the relay server that we're still alive.\nfunc (c *Client) buildResponses(in <-chan []byte, resp *pb.HttpResponse, out chan<- *pb.HttpResponse) {\n\tdefer close(out)\n\ttimer := time.NewTimer(c.config.BackendResponseTimeout)\n\ttimeouts := 0\n\n\t// TODO(haukeheibel): Why are we not simply reading the entire body? Why the chunking?\n\tfor {\n\t\tselect {\n\t\tcase b, more := <-in:\n\t\t\tresp.Body = append(resp.Body, b...)\n\t\t\tif !more {\n\t\t\t\tif debugLogs {\n\t\t\t\t\tslog.Info(\"Posting final response to relay\",\n\t\t\t\t\t\tslog.String(\"ID\", *resp.Id), slog.Int(\"ByteCount\", len(resp.Body)))\n\t\t\t\t}\n\t\t\t\tresp.Eof = proto.Bool(true)\n\t\t\t\tout <- resp\n\t\t\t\treturn\n\t\t\t} else if len(resp.Body) > c.config.MaxChunkSize {\n\t\t\t\tif debugLogs {\n\t\t\t\t\tslog.Info(\"Posting intermediate response to relay\",\n\t\t\t\t\t\tslog.String(\"ID\", *resp.Id), slog.Int(\"ByteCount\", len(resp.Body)))\n\t\t\t\t}\n\t\t\t\tout <- resp\n\t\t\t\tresp = &pb.HttpResponse{Id: resp.Id}\n\t\t\t\ttimeouts = 0\n\t\t\t}\n\t\tcase <-timer.C:\n\t\t\ttimer.Reset(c.config.BackendResponseTimeout)\n\t\t\ttimeouts += 1\n\t\t\t// We send an (empty) response after 30 timeouts as a keep-alive packet.\n\t\t\tif len(resp.Body) > 0 || resp.StatusCode != nil || timeouts > 30 {\n\t\t\t\tif debugLogs {\n\t\t\t\t\tslog.Info(\"Posting partial response to relay\",\n\t\t\t\t\t\tslog.String(\"ID\", *resp.Id), slog.Int(\"ByteCount\", len(resp.Body)))\n\t\t\t\t}\n\t\t\t\tout <- resp\n\t\t\t\tresp = &pb.HttpResponse{Id: resp.Id}\n\t\t\t\ttimeouts = 0\n\t\t\t}\n\t\t}\n\t}\n}\n\n// postErrorResponse resolves the client's request in case of an internal error.\n// This is not strictly necessary, but avoids kubectl hanging in such cases. As\n// this is best-effort, errors posting the response are logged and ignored.\nfunc (c *Client) postErrorResponse(remote *http.Client, id string, message string) {\n\tresp := &pb.HttpResponse{\n\t\tId:         proto.String(id),\n\t\tStatusCode: proto.Int32(http.StatusInternalServerError),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"Content-Type\"),\n\t\t\tValue: proto.String(\"text/plain\"),\n\t\t}},\n\t\tBody: []byte(message),\n\t\tEof:  proto.Bool(true),\n\t}\n\tif err := c.postResponse(remote, resp); err != nil {\n\t\tslog.Error(\"Failed to post error response to relay\",\n\t\t\tslog.String(\"ID\", *resp.Id), ilog.Err(err))\n\t}\n}\n\n// streamToBackend streams data from the client (eg kubectl) to the\n// backend. For example, when using `kubectl exec` this handles stdin.\n// It fails permanently and closes the backend connection on any failure, as\n// the relay-server doesn't have sufficiently advanced flow control to recover\n// from dropped/duplicate \"packets\".\nfunc (c *Client) streamToBackend(remote *http.Client, id string, backendWriter io.WriteCloser) {\n\t// Close the backend connection on stream failure. This should cause the\n\t// response stream to end and prevent the client from hanging in the case\n\t// of an error in the request stream.\n\tdefer backendWriter.Close()\n\n\tstreamURL := (&url.URL{\n\t\tScheme:   c.config.RelayScheme,\n\t\tHost:     c.config.RelayAddress,\n\t\tPath:     c.config.RelayPrefix + \"/server/requeststream\",\n\t\tRawQuery: \"id=\" + id,\n\t}).String()\n\tfor {\n\t\t// Get data from the \"request stream\", then copy it to the backend.\n\t\t// We use a Post with empty body to avoid caching.\n\t\tresp, err := remote.Post(streamURL, \"text/plain\", http.NoBody)\n\t\tif err != nil {\n\t\t\t// TODO(rodrigoq): detect transient failure and retry w/ backoff?\n\t\t\t// e.g. \"server status Request Timeout: No request received within timeout\"\n\t\t\tslog.Error(\"Failed to get request stream\",\n\t\t\t\tslog.String(\"ID\", id), ilog.Err(err))\n\t\t\treturn\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\tif resp.StatusCode == http.StatusGone {\n\t\t\tif debugLogs {\n\t\t\t\tslog.Info(\"End of request stream\", slog.String(\"ID\", id))\n\t\t\t}\n\t\t\treturn\n\t\t} else if resp.StatusCode != http.StatusOK {\n\t\t\tmsg, err := io.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\tmsg = []byte(fmt.Sprintf(\"<failed to read response body: %v>\", err))\n\t\t\t}\n\t\t\tif debugLogs {\n\t\t\t\tslog.Info(\"Relay server request stream responded\",\n\t\t\t\t\tslog.String(\"ID\", id),\n\t\t\t\t\tslog.String(\"Status\", http.StatusText(resp.StatusCode)),\n\t\t\t\t\tslog.String(\"Message\", string(msg)))\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif n, err := io.Copy(backendWriter, resp.Body); err != nil {\n\t\t\tslog.Error(\"Failed to write to backend:\",\n\t\t\t\tslog.String(\"ID\", id), ilog.Err(err))\n\t\t\treturn\n\t\t} else {\n\t\t\tif debugLogs {\n\t\t\t\tslog.Info(\"Wrote to backend\",\n\t\t\t\t\tslog.String(\"ID\", id), slog.Int64(\"ByteCount\", n))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (c *Client) handleRequest(remote *http.Client, local *http.Client, pbreq *pb.HttpRequest) {\n\tts := time.Now()\n\tid := *pbreq.Id\n\treq, err := c.createBackendRequest(pbreq)\n\tif err != nil {\n\t\tc.postErrorResponse(remote, id, fmt.Sprintf(\"Failed to create request for backend: %v\", err))\n\t}\n\t// Measure edge processing time.\n\tf := &tracecontext.HTTPFormat{}\n\tctx := req.Context()\n\tvar span *trace.Span\n\tif sctx, ok := f.SpanContextFromRequest(req); ok {\n\t\tctx, span = trace.StartSpanWithRemoteParent(ctx, \"Recv.\"+req.URL.Path, sctx)\n\t} else {\n\t\tctx, span = trace.StartSpan(ctx, \"Recv.\"+req.URL.Path)\n\t}\n\taddServiceName(span)\n\tdefer span.End()\n\n\tresp, hresp, err := makeBackendRequest(ctx, local, req, id)\n\tif err != nil {\n\t\t// Even if we couldn't handle the backend request, send an\n\t\t// answer to the relay that signals the error.\n\t\terrorMessage := fmt.Sprintf(\"Backend request failed with error: %v\", err)\n\t\tslog.Error(\"BackendRequest\",\n\t\t\tslog.String(\"ID\", id), slog.String(\"Message\", errorMessage))\n\t\tc.postErrorResponse(remote, id, errorMessage)\n\t\treturn\n\t}\n\n\tif *resp.StatusCode == http.StatusSwitchingProtocols {\n\t\t// A 101 Switching Protocols response means that the request will be\n\t\t// used for bidirectional streaming, so start a goroutine to stream\n\t\t// from client to backend.\n\t\tbodyWriter, ok := hresp.Body.(io.WriteCloser)\n\t\tif !ok {\n\t\t\tslog.Warn(\"Error: 101 Switching Protocols response with non-writable body.\")\n\t\t\tslog.Warn(\"       This occurs when using Go <1.12 or when http.Client.Timeout > 0.\")\n\t\t\tc.postErrorResponse(remote, id, \"Backend returned 101 Switching Protocols, which is not supported.\")\n\t\t\treturn\n\t\t}\n\t\t// Stream stdin from remote to backend\n\t\tgo c.streamToBackend(remote, id, bodyWriter)\n\t} else {\n\t\t// `streamToBackend` will close `hresp.Body` but it is only called on websocket connections.\n\t\t// We need to close it here for http connections.\n\t\tdefer hresp.Body.Close()\n\t}\n\n\tctx, respChSpan := trace.StartSpan(ctx, \"Building (chunked) response channel\")\n\taddServiceName(respChSpan)\n\n\tbodyChannel := make(chan []byte)\n\tresponseChannel := make(chan *pb.HttpResponse)\n\t// Stream stdout from backend to bodyChannel\n\tgo c.streamBytes(*resp.Id, hresp.Body, bodyChannel)\n\t// collect data from bodyChannel and send to remote (relay-server)\n\tgo c.buildResponses(bodyChannel, resp, responseChannel)\n\n\trespChSpan.End()\n\n\texponentialBackoff := backoff.ExponentialBackOff{\n\t\tInitialInterval:     time.Second,\n\t\tRandomizationFactor: 0,\n\t\tMultiplier:          2,\n\t\tMaxInterval:         10 * time.Second,\n\t\tMaxElapsedTime:      0,\n\t\tClock:               backoff.SystemClock,\n\t}\n\n\t// This call here blocks until all data from the bodyChannel has been read.\n\tfor resp := range responseChannel {\n\t\t_, respCh := trace.StartSpan(ctx, \"Sending response from channel\")\n\t\taddServiceName(respCh)\n\t\tdefer respCh.End()\n\n\t\t// Q(hauke): do we really need exponential backoff in the relay?\n\t\texponentialBackoff.Reset()\n\t\terr := backoff.RetryNotify(\n\t\t\tfunc() error {\n\t\t\t\tif len(hresp.Trailer) > 0 {\n\t\t\t\t\tslog.Info(\"Trailers\",\n\t\t\t\t\t\tslog.String(\"ID\", *resp.Id),\n\t\t\t\t\t\tslog.String(\"Trailer\", fmt.Sprintf(\"%+v\", hresp.Trailer)))\n\t\t\t\t\tresp.Trailer = append(resp.Trailer, marshalHeader(&hresp.Trailer)...)\n\t\t\t\t}\n\t\t\t\tif resp.Eof != nil && *resp.Eof {\n\t\t\t\t\tduration := timeSince(ts)\n\t\t\t\t\tresp.BackendDurationMs = proto.Int64(duration.Milliseconds())\n\t\t\t\t\t// see makeBackendRequest()\n\t\t\t\t\turlPath := strings.TrimPrefix(*pbreq.Url, \"http://invalid\")\n\t\t\t\t\tslog.Debug(\"Backend request\",\n\t\t\t\t\t\tslog.String(\"ID\", *resp.Id),\n\t\t\t\t\t\tslog.Float64(\"Duration\", duration.Seconds()),\n\t\t\t\t\t\tslog.String(\"Path\", urlPath))\n\t\t\t\t} else {\n\t\t\t\t\t// Q(hauke): When are we ending up in this branch?\n\t\t\t\t\t// What are the semantics and why are we not setting a request duration?\n\t\t\t\t\t// Even in a streaming case I would expect a duration which represents the\n\t\t\t\t\t// processing time of the last item.\n\t\t\t\t}\n\t\t\t\treturn c.postResponse(remote, resp)\n\t\t\t},\n\t\t\tbackoff.WithMaxRetries(&exponentialBackoff, 10),\n\t\t\tfunc(err error, _ time.Duration) {\n\t\t\t\tslog.Error(\"Failed to post response to relay\",\n\t\t\t\t\tslog.String(\"ID\", *resp.Id), ilog.Err(err))\n\t\t\t},\n\t\t)\n\t\t// Any error suggests the request should be aborted.\n\t\t// A missing chunk will cause clients to receive corrupted data, in most cases it is better\n\t\t// to close the connection to avoid that.\n\t\tif err != nil {\n\t\t\tslog.Error(\"Closing backend connection\",\n\t\t\t\tslog.String(\"ID\", *resp.Id), ilog.Err(err))\n\t\t\t// This is also closed in streamToBackend.\n\t\t\t// Closing here too to ensure the disconnect propagates faster.\n\t\t\thresp.Body.Close()\n\t\t\t// Drain the response channel to avoid blocking buildResponses.\n\t\t\tfor range responseChannel {\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (c *Client) localProxy(remote, local *http.Client) error {\n\t// Read pending request from the relay-server.\n\trelayURL := c.buildRelayURL()\n\n\tvar req *pb.HttpRequest = nil\n\n\texponentialBackoff := backoff.ExponentialBackOff{\n\t\tInitialInterval:     100 * time.Millisecond,\n\t\tRandomizationFactor: 0,\n\t\tMultiplier:          1.5,\n\t\tMaxInterval:         10 * time.Second,\n\t\tMaxElapsedTime:      60 * time.Second,\n\t\tClock:               backoff.SystemClock,\n\t}\n\n\terr := backoff.RetryNotify(func() error {\n\t\tvar err error\n\t\treq, err = c.getRequest(remote, relayURL)\n\t\tif errors.Is(err, ErrTimeout) {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\treturn err\n\t}, &exponentialBackoff, func(err error, _ time.Duration) {\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\t\tif errors.Is(err, ErrForbidden) {\n\t\t\tslog.Error(\"failed to authenticate to cloud-api, restarting\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t} else if errors.Is(err, syscall.ECONNREFUSED) {\n\t\t\tslog.Warn(\"Failed to connect to relay server. Retrying.\")\n\t\t\treturn\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Forward the request to the backend.\n\tgo c.handleRequest(remote, local, req)\n\treturn nil\n}\n\nfunc (c *Client) localProxyWorker(remote, local *http.Client) {\n\tslog.Info(\"Starting to relay server request loop\", slog.String(\"ServerName\", c.config.ServerName))\n\tfor {\n\t\terr := c.localProxy(remote, local)\n\t\tif err != nil && !errors.Is(err, ErrTimeout) {\n\t\t\tslog.Error(\"localProxy\", ilog.Err(err))\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t}\n}\n\nfunc (c *Client) buildRelayURL() string {\n\tquery := url.Values{}\n\tquery.Add(\"server\", c.config.ServerName)\n\trelayURL := url.URL{\n\t\tScheme:   c.config.RelayScheme,\n\t\tHost:     c.config.RelayAddress,\n\t\tPath:     c.config.RelayPrefix + \"/server/request\",\n\t\tRawQuery: query.Encode(),\n\t}\n\treturn relayURL.String()\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-client/client/client_test.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\tpb \"github.com/googlecloudrobotics/core/src/proto/http-relay\"\n\n\t. \"github.com/onsi/gomega\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"gopkg.in/h2non/gock.v1\"\n)\n\nfunc assertMocksDoneWithin(t *testing.T, d time.Duration) {\n\tfor start := time.Now(); time.Since(start) < d; {\n\t\tif gock.IsDone() {\n\t\t\treturn\n\t\t}\n\t\ttime.Sleep(time.Millisecond)\n\t}\n\tfor _, m := range gock.Pending() {\n\t\tt.Errorf(\"mock still pending after %s: %v\", d, m.Request().URLStruct)\n\t}\n}\n\nfunc TestAssertMocksDoneWithin_SucceedsWhenMocksAreDone(t *testing.T) {\n\tassertMocksDoneWithin(t, time.Millisecond)\n}\n\nfunc TestAssertMocksDoneWithin_FailsWhenMocksNotDone(t *testing.T) {\n\tdefer gock.Off()\n\tgock.New(\"https://localhost:8081\")\n\tfaket := &testing.T{}\n\tassertMocksDoneWithin(faket, time.Millisecond)\n\tif !faket.Failed() {\n\t\tt.Errorf(\"assertMocksDoneWithin didn't trigger an error despite outstanding mocks\")\n\t}\n}\n\nfunc TestLocalProxy(t *testing.T) {\n\t// Hot patch: gock refuses to match bodies with unknown content-types by default.\n\tgock.BodyTypes = append(gock.BodyTypes, \"application/vnd.google.protobuf;proto=cloudrobotics.http_relay.v1alpha1.HttpResponse\")\n\tdefer gock.Off()\n\n\t// We expect the response below to always contain 0 milliseconds.\n\ttimeSince = func(t time.Time) time.Duration { return 0 * time.Millisecond }\n\n\treq, _ := proto.Marshal(&pb.HttpRequest{\n\t\tId:     proto.String(\"15\"),\n\t\tMethod: proto.String(\"GET\"),\n\t\tUrl:    proto.String(\"http://invalid/foo/bar?a=b\"),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\")}},\n\t\tBody: []byte(\"thebody\"),\n\t})\n\tresp, _ := proto.Marshal(&pb.HttpResponse{\n\t\tId:         proto.String(\"15\"),\n\t\tStatusCode: proto.Int32(201),\n\t\tHeader: []*pb.HttpHeader{\n\t\t\t{\n\t\t\t\tName:  proto.String(\"Priority\"),\n\t\t\t\tValue: proto.String(\"High\"),\n\t\t\t},\n\t\t},\n\t\tBody:              []byte(\"theresponsebody\"),\n\t\tEof:               proto.Bool(true),\n\t\tBackendDurationMs: proto.Int64(0),\n\t})\n\tgock.New(\"https://localhost:8081\").\n\t\tGet(\"/server/request\").\n\t\tMatchParam(\"server\", \"foo\").\n\t\tReply(200).\n\t\tBodyString(string(req))\n\tgock.New(\"https://localhost:8080\").\n\t\tGet(\"/foo/bar\").\n\t\tMatchParam(\"a\", \"b\").\n\t\tMatchHeader(\"X-GFE\", \"google.com\").\n\t\tBodyString(\"thebody\").\n\t\tReply(201).\n\t\tSetHeader(\"Priority\", \"High\").\n\t\tBodyString(\"theresponsebody\")\n\tgock.New(\"https://localhost:8081\").\n\t\tPost(\"/server/response\").\n\t\tBody(bytes.NewReader(resp)).\n\t\tReply(200)\n\n\tconfig := DefaultClientConfig()\n\tconfig.ServerName = \"foo\"\n\tclient := NewClient(config)\n\terr := client.localProxy(&http.Client{}, &http.Client{})\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\tassertMocksDoneWithin(t, 10*time.Second)\n}\n\nfunc TestBackendError(t *testing.T) {\n\t// Hot patch: gock refuses to match bodies with unknown content-types by default.\n\tgock.BodyTypes = append(gock.BodyTypes, \"application/vnd.google.protobuf;proto=cloudrobotics.http_relay.v1alpha1.HttpResponse\")\n\tdefer gock.Off()\n\n\t// We expect the response below to always contain 0 milliseconds.\n\ttimeSince = func(t time.Time) time.Duration { return 0 * time.Millisecond }\n\n\t// The pending request on the relay-server side.\n\treq, _ := proto.Marshal(&pb.HttpRequest{\n\t\tId:     proto.String(\"15\"),\n\t\tMethod: proto.String(\"GET\"),\n\t\tUrl:    proto.String(\"http://invalid/foo/bar?a=b\"),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\")}},\n\t\tBody: []byte(\"thebody\"),\n\t})\n\n\tresp, _ := proto.Marshal(&pb.HttpResponse{\n\t\tId:                proto.String(\"15\"),\n\t\tStatusCode:        proto.Int32(400),\n\t\tBody:              []byte(\"theresponsebody\"),\n\t\tEof:               proto.Bool(true),\n\t\tBackendDurationMs: proto.Int64(0),\n\t})\n\n\trelayServerAddress := \"https://localhost:8081\"\n\tbackendServerAddress := \"https://localhost:8080\"\n\n\t// Mocks the response from the relay server from which we are getting\n\t// the initial data.\n\tgock.New(relayServerAddress).\n\t\tGet(\"/server/request\").\n\t\tMatchParam(\"server\", \"foo\").\n\t\tReply(200).\n\t\tBodyString(string(req))\n\n\t// Mocks the response from the backend server to which we relayed data.\n\tgock.New(backendServerAddress).\n\t\tGet(\"/foo/bar\").\n\t\tMatchParam(\"a\", \"b\").\n\t\tMatchHeader(\"X-GFE\", \"google.com\").\n\t\tBodyString(\"thebody\").\n\t\tReply(400).\n\t\tBodyString(\"theresponsebody\")\n\n\t// Mocks the response from the realy-server after having received the\n\t// actual backend response.\n\tgock.New(relayServerAddress).\n\t\tPost(\"/server/response\").\n\t\tBody(bytes.NewReader(resp)).\n\t\tReply(200)\n\n\tconfig := DefaultClientConfig()\n\tconfig.ServerName = \"foo\"\n\tclient := NewClient(config)\n\n\t// localProxy ...\n\t// 1. pulls a request from the realy-server (/server/request)\n\t// 2. send that request to the backend server (here localhost:8080/foo/bar?a=b)\n\t// 3. retrieves the response from the backend and sends it to the relay-server\n\terr := client.localProxy(&http.Client{}, &http.Client{})\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\tassertMocksDoneWithin(t, 10*time.Second)\n}\n\nfunc TestServerTimeout(t *testing.T) {\n\t// Hot patch: gock refuses to match bodies with application/octet-data\n\t// by default.\n\tgock.BodyTypes = append(gock.BodyTypes, \"application/octet-data\")\n\tdefer gock.Off()\n\n\treq, _ := proto.Marshal(&pb.HttpRequest{\n\t\tId:     proto.String(\"15\"),\n\t\tMethod: proto.String(\"GET\"),\n\t\tUrl:    proto.String(\"http://invalid/foo/bar?a=b\"),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\")}},\n\t\tBody: []byte(\"thebody\"),\n\t})\n\tgock.New(\"https://localhost:8081\").\n\t\tGet(\"/server/request\").\n\t\tMatchParam(\"server\", \"foo\").\n\t\tReply(408).\n\t\tBodyString(string(req))\n\n\tconfig := DefaultClientConfig()\n\tconfig.ServerName = \"foo\"\n\tclient := NewClient(config)\n\terr := client.localProxy(&http.Client{}, &http.Client{})\n\tif err != ErrTimeout {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\tassertMocksDoneWithin(t, 10*time.Second)\n}\n\nfunc TestBuildResponsesTimesOut(t *testing.T) {\n\tg := NewGomegaWithT(t)\n\tbodyChannel := make(chan []byte)\n\tresponseChannel := make(chan *pb.HttpResponse)\n\tresp := &pb.HttpResponse{\n\t\tId:         proto.String(\"20\"),\n\t\tStatusCode: proto.Int32(200),\n\t}\n\tconfig := DefaultClientConfig()\n\tconfig.BackendResponseTimeout = 10 * time.Millisecond\n\tclient := NewClient(config)\n\tgo client.buildResponses(bodyChannel, resp, responseChannel)\n\tbodyChannel <- []byte(\"foo\")\n\tresp = <-responseChannel\n\tg.Expect(*resp.Id).To(Equal(\"20\"))\n\tg.Expect(*resp.StatusCode).To(Equal(int32(200)))\n\tg.Expect(string(resp.Body)).To(Equal(\"foo\"))\n\tg.Expect(resp.Eof).To(BeNil())\n\tbodyChannel <- []byte(\"bar\")\n\tresp = <-responseChannel\n\tg.Expect(*resp.Id).To(Equal(\"20\"))\n\tg.Expect(resp.StatusCode).To(BeNil())\n\tg.Expect(string(resp.Body)).To(Equal(\"bar\"))\n\tg.Expect(resp.Eof).To(BeNil())\n\tclose(bodyChannel)\n\tresp = <-responseChannel\n\tg.Expect(*resp.Id).To(Equal(\"20\"))\n\tg.Expect(resp.StatusCode).To(BeNil())\n\tg.Expect(string(resp.Body)).To(Equal(\"\"))\n\tg.Expect(*resp.Eof).To(Equal(true))\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-client/main.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package main runs a local HTTP relay client.\n//\n// See the documentation of ../http-relay-server/main.go for details about\n// the system architecture. In a nutshell, this program pulls serialized HTTP\n// requests from a remote relay server, redirects them to a local backend, and\n// posts the serialized response to the relay server.\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\n\t\"contrib.go.opencensus.io/exporter/stackdriver\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-client/client\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"go.opencensus.io/trace\"\n)\n\nvar (\n\tconfig client.ClientConfig\n\n\tstackdriverProjectID string\n\tlogLevel             int\n\tpprofPort            int\n)\n\nfunc init() {\n\tconfig = client.DefaultClientConfig()\n\n\t// We set the default values for all command line flags to be equal to the\n\t// values in the default client config to ensure consistency between the two.\n\tflag.StringVar(&config.BackendScheme, \"backend_scheme\", config.BackendScheme,\n\t\t\"Connection scheme (http, https) for connection from relay \"+\n\t\t\t\"client to backend server\")\n\tflag.StringVar(&config.BackendAddress, \"backend_address\", config.BackendAddress,\n\t\t\"Hostname of the backend server as seen by the relay client\")\n\tflag.StringVar(&config.BackendPath, \"backend_path\", config.BackendPath,\n\t\t\"Path prefix for backend requests (default: none)\")\n\tflag.BoolVar(&config.PreserveHost, \"preserve_host\", config.PreserveHost,\n\t\t\"Preserve Host header of the original request for \"+\n\t\t\t\"compatibility with cross-origin request checks.\")\n\tflag.StringVar(&config.RelayScheme, \"relay_scheme\", config.RelayScheme,\n\t\t\"Connection scheme (http, https) for connection from relay \"+\n\t\t\t\"client to relay server\")\n\tflag.StringVar(&config.RelayAddress, \"relay_address\", config.RelayAddress,\n\t\t\"Hostname of the relay server as seen by the relay client\")\n\tflag.StringVar(&config.RelayPrefix, \"relay_prefix\", config.RelayPrefix,\n\t\t\"Path prefix for the relay server\")\n\tflag.StringVar(&config.ServerName, \"server_name\", config.ServerName,\n\t\t\"Fetch requests from the relay server for this server name\")\n\tflag.StringVar(&config.AuthenticationTokenFile, \"authentication_token_file\", config.AuthenticationTokenFile,\n\t\t\"File with authentication token for backend requests\")\n\tflag.StringVar(&config.RootCAFile, \"root_ca_file\", config.RootCAFile,\n\t\t\"File with root CA cert for SSL\")\n\tflag.IntVar(&config.MaxChunkSize, \"max_chunk_size\", config.MaxChunkSize,\n\t\t\"Max size of data in bytes to accumulate before sending to the peer\")\n\tflag.IntVar(&config.BlockSize, \"block_size\", config.BlockSize,\n\t\t\"Size of i/o buffer in bytes\")\n\tflag.IntVar(&config.NumPendingRequests, \"num_pending_requests\", config.NumPendingRequests,\n\t\t\"Number of pending http requests to the relay\")\n\tflag.IntVar(&config.MaxIdleConnsPerHost, \"max_idle_conns_per_host\", config.MaxIdleConnsPerHost,\n\t\t\"The maximum number of idle (keep-alive) connections to keep per-host\")\n\tflag.BoolVar(&config.DisableHttp2, \"disable_http2\", config.DisableHttp2,\n\t\t\"Disable http2 protocol usage (e.g. for channels that use special streaming protocols such as SPDY).\")\n\tflag.BoolVar(&config.ForceHttp2, \"force_http2\", config.ForceHttp2,\n\t\t\"Force enable http2 protocol usage through the use of go's http2 transport (e.g. when relaying grpc).\")\n\tflag.BoolVar(&config.DisableAuthForRemote, \"disable_auth_for_remote\", config.DisableAuthForRemote,\n\t\t\"Disable auth when talking to the relay server for local testing.\")\n\n\t// The stackdriver project ID is a client independent variable and so we\n\t// initialize it independently.\n\tflag.StringVar(&stackdriverProjectID, \"trace-stackdriver-project-id\", \"\",\n\t\t\"If not empty, traces will be uploaded to this Google Cloud Project.\")\n\tflag.IntVar(&logLevel, \"log_level\", int(slog.LevelInfo),\n\t\t\"the log message level required to be logged\")\n\tflag.IntVar(&pprofPort, \"pprof_port\", 0, \"If non-zero, serves pprof endpoints on this port.\")\n}\n\nfunc main() {\n\tflag.Parse()\n\tif pprofPort != 0 {\n\t\tgo func() {\n\t\t\tslog.Info(\"Starting pprof server\", slog.Int(\"port\", pprofPort))\n\t\t\terr := http.ListenAndServe(fmt.Sprintf(\":%d\", pprofPort), nil)\n\t\t\tslog.Error(\"pprof server failed\", ilog.Err(err))\n\t\t}()\n\t}\n\tlogHandler := ilog.NewLogHandler(slog.Level(logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tif stackdriverProjectID != \"\" {\n\t\tsd, err := stackdriver.NewExporter(stackdriver.Options{\n\t\t\tProjectID: stackdriverProjectID,\n\t\t})\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to create the Stackdriver exporter\", slog.String(\"Project\", stackdriverProjectID), ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t} else {\n\t\t\ttrace.RegisterExporter(sd)\n\t\t\tdefer sd.Flush()\n\t\t}\n\t}\n\n\tclient := client.NewClient(config)\n\tclient.Start()\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-server\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/cmd/http-relay-server/server:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n        \"@io_opencensus_go_contrib_exporter_stackdriver//:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"http-relay-server-app\",\n    embed = [\":go_default_library\"],\n)\n\npkg_tar(\n    name = \"http-relay-server-image-layer\",\n    srcs = [\":http-relay-server-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"http-relay-server-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/http-relay-server-app\"],\n    tars = [\":http-relay-server-image-layer\"],\n)\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/README.md",
    "content": "# HTTP Relay Server\n\nThe http-relay-server multiplexes HTTP requests between user-clients and backends (robots) via a relay-client. It exists to make HTTP endpoints on robots accessible without requiring a public endpoint on the robot itself.\n\n## How it works\n\nIt binds to a public endpoint accessible by both user-client and backend, and works together with a relay-client that's colocated with the backend. This allows multiple backends to be accessible through a single relay-server instance, and supports multiple concurrent user-clients.\n\n```mermaid\nflowchart LR\n    subgraph \"LAN (User)\"\n        user-client\n    end\n\n    subgraph Internet\n        relay-server\n    end\n\n    subgraph \"LAN (Robot)\"\n        relay-client\n        backend\n    end\n\n    user-client -->|HTTP Request| relay-server\n    %% Workaround https://github.com/mermaid-js/mermaid/issues/3208\n    relay-server ~~~ relay-client -->|Poll | relay-server\n    relay-client ~~~ relay-server\n    relay-client -->|Forward Request| backend\n```\n\nThe relay-server is multiplexing: It allows multiple relay-clients to\nconnect under unique names, each handling requests for a subpath of `/client`.\nAlternatively (e.g. for gRPC connections) the backend can be selected by\nomitting the client prefix and passing an `X-Server-Name` header.\n\n### Sequence of operations\n\n1. User-client makes request on `/client/$foo/$request`.\n2. Relay-server assigns an ID and stores request (with path `$request`) in\n   memory. It keeps the user-client's request pending.\n3. Relay-client requests `/server/request?server=$foo`\n4. Relay-server responds with stored request (or timeout if no request comes\n   in within the next 30 sec).\n5. Relay-client makes the stored request to backend.\n6. Backend replies.\n7. Relay-client posts backend's reply to `/server/response`.\n8. Relay-server responds to client's request with backend's reply.\n\nFor some requests (e.g. `kubectl exec`), the backend responds with\n`101 Switching Protocols`, resulting in the following operations:\n\n1. Relay-server responds to client's request with backend's 101 reply.\n2. User-client sends bytes from stdin to the relay-server.\n3. Relay-client requests `/server/requeststream?id=$id`.\n4. Relay-server responds with stdin bytes from client.\n5. Relay-client sends stdin bytes to backend.\n6. Backend sends stdout bytes to relay-client.\n7. Relay-client posts stdout bytes to `/server/response`.\n8. Relay-server sends stdout bytes to the client.\n\nThis simplified graphic shows the back-and-forth for an `exec` request:\n\n```mermaid\nsequenceDiagram\n    participant user-client as user-client<br/>(kubectl exec)\n    participant relay-server\n    participant relay-client\n    participant backend as backend<br/>(k8s apiserver)\n\n    user-client->>relay-server: POST /exec\n    relay-client->>relay-server: GET /request\n    relay-server-->>relay-client: exec\n    relay-client->>backend: POST /exec\n    activate backend\n    backend-->>relay-client: 101 Switching Protocols\n    relay-client->>relay-server: POST /response (101)\n    relay-server-->>user-client: 101 Switching Protocols\n    \n    user-client->>relay-server: stdin\n    relay-client->>relay-server: POST /requeststream?id=$id\n    relay-server-->>relay-client: stdin\n    relay-client->>backend: stdin\n    backend-->>relay-client: stdout\n    deactivate backend\n    relay-client->>relay-server: POST /response (stdout)\n    relay-server-->>user-client: stdout\n```\n\nThe relay-client side implementation is in `../http-relay-client`.\n\n## Tested capabilities\n\nThe http-relay-server was originally designed as a way to use kubectl against remote clusters.\nIt traverses firewalls by only making outbound requests to the public internet from both the user client (eg kubectl, browser) and the remote cluster.\nIt has been tested with the following traffic:\n\n- HTTP 1.1 & 2 from web browsers (including bidirectional streaming with websockets)\n- HTTP 1.1 from kubectl, including streaming response bodies for `kubectl logs`\n- SPDY from kubectl (via HTTP 101 Switching Protocols) for `kubectl exec`\n- unidirectional gRPC (HTTP2 cleartext and HTTP trailers)\n\nThe following is known not to work:\n\n- streaming gRPC fails with `rpc error: code = Internal desc = stream terminated by RST_STREAM with error code: PROTOCOL_ERROR`, root cause unknown\n\nThe following has not been tested:\n\n- HTTP 1.1 streaming request body (`Transfer-Encoding: chunked` in the request header)\n\n## Flags\n\n*   `--port`: Port number to listen on (default: 80).\n*   `--block_size`: Size of i/o buffer in bytes (default: 10240).\n*   `--inactive_request_timeout`: Timeout for inactive requests (default: 60s). In particular, this sets a limit on how long the backend can wait before writing headers and the response status.\n\n## Configuration\n\n### Nginx Timeout\n\nIf you are running the relay server behind Nginx, ensure that the proxy read timeout on Nginx is set such that Nginx doesn't time out before the http-relay-server does.\n\nSpecifically, the `nginx.ingress.kubernetes.io/proxy-read-timeout` annotation (or `proxy_read_timeout` directive in nginx config) should be set to a value larger than `--inactive_request_timeout`.\n\nFor example, if `--inactive_request_timeout` is set to `60s`, you might set `nginx.ingress.kubernetes.io/proxy-read-timeout` to `75s`.\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/main.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package main runs a multiplexing HTTP relay server.\n//\n// It exists to make HTTP endpoints on robots accessible without a public\n// endpoint. It binds to a public endpoint accessible by both user-client and\n// backend and works together with a relay-client that's colocated with the\n// backend.\n//\n// For more details, see README.md.\npackage main\n\nimport (\n\t\"flag\"\n\t\"log/slog\"\n\t\"os\"\n\n\t\"contrib.go.opencensus.io/exporter/stackdriver\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-server/server\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"go.opencensus.io/trace\"\n)\n\nvar (\n\tport      = flag.Int(\"port\", server.DefaultPort, \"Port number to listen on\")\n\tblockSize = flag.Int(\"block_size\", server.DefaultBlockSize,\n\t\t\"Size of i/o buffer in bytes\")\n\tstackdriverProjectID = flag.String(\"trace-stackdriver-project-id\", \"\",\n\t\t\"If not empty, traces will be uploaded to this Google Cloud Project.\")\n\tlogLevel = flag.Int(\"log_level\", int(slog.LevelInfo),\n\t\t\"the log message level required to be logged\")\n\tinactiveRequestTimeout = flag.Duration(\"inactive_request_timeout\", server.DefaultInactiveRequestTimeout,\n\t\t\"Timeout for inactive requests. In particular, this sets a limit on how long the backend can wait before writing headers and the response status.\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tlogHandler := ilog.NewLogHandler(slog.Level(*logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tif *stackdriverProjectID != \"\" {\n\t\tsd, err := stackdriver.NewExporter(stackdriver.Options{\n\t\t\tProjectID: *stackdriverProjectID,\n\t\t})\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to create the Stackdriver exporter\", slog.String(\"Project\", *stackdriverProjectID), ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t} else {\n\t\t\ttrace.RegisterExporter(sd)\n\t\t\tdefer sd.Flush()\n\t\t}\n\t}\n\n\tserver := server.NewServer(server.Config{\n\t\tPort:                   *port,\n\t\tBlockSize:              *blockSize,\n\t\tInactiveRequestTimeout: *inactiveRequestTimeout,\n\t})\n\tserver.Start()\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/server/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"broker.go\",\n        \"server.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-server/server\",\n    deps = [\n        \"//src/proto/http-relay:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library\",\n        \"@io_opencensus_go//plugin/ochttp:go_default_library\",\n        \"@io_opencensus_go//plugin/ochttp/propagation/tracecontext:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n        \"@org_golang_google_protobuf//proto:go_default_library\",\n        \"@org_golang_x_net//http2:go_default_library\",\n        \"@org_golang_x_net//http2/h2c:go_default_library\",\n        \"@org_golang_x_sync//errgroup:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\n        \"broker_test.go\",\n        \"server_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/proto/http-relay:go_default_library\",\n        \"@com_github_getlantern_httptest//:go_default_library\",\n        \"@org_golang_google_protobuf//proto:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/server/broker.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tpb \"github.com/googlecloudrobotics/core/src/proto/http-relay\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nvar (\n\tbrokerRequests = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"broker_requests\",\n\t\t\tHelp: \"Number of requests to the broker\",\n\t\t},\n\t\t[]string{\"method\", \"backend\"},\n\t)\n\tbrokerResponses = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"broker_responses\",\n\t\t\tHelp: \"Number of responses from the broker\",\n\t\t},\n\t\t[]string{\"method\", \"result\", \"backend\"},\n\t)\n\tbrokerResponseDurations = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"broker_responses_durations\",\n\t\t\tHelp: \"Time from request to final response in s\",\n\t\t},\n\t\t[]string{\"method\", \"backend\"},\n\t)\n\tbrokerBackendResponseDurations = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"broker_backend_responses_durations\",\n\t\t\tHelp: \"Time from backend request to final response in s\",\n\t\t},\n\t\t[]string{\"method\", \"backend\"},\n\t)\n\tbrokerOverheadDurations = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"broker_overhead_durations\",\n\t\t\tHelp: \"Extra time spend between relay server and client in s\",\n\t\t},\n\t\t[]string{\"method\", \"backend\"},\n\t)\n)\n\nfunc init() {\n\tprometheus.MustRegister(brokerRequests)\n\tprometheus.MustRegister(brokerResponses)\n\tprometheus.MustRegister(brokerResponseDurations)\n\tprometheus.MustRegister(brokerBackendResponseDurations)\n\tprometheus.MustRegister(brokerOverheadDurations)\n}\n\ntype pendingResponse struct {\n\t// This channel is used to communicate data between the backend and user-client for\n\t// bidirectional streaming connections.\n\trequestStream chan []byte\n\t// This mutex should be locked when writing to `requestStream``\n\trequestStreamMutex sync.Mutex\n\n\t// This channel is used to communicate data between the backend and user-client.\n\t// The user-client sends a hanging request to the relay-server which blocks until\n\t// data is received on the response channel.\n\tresponseStream chan *pb.HttpResponse\n\t// This mutex should be locked when writing to `responseStream``\n\tsendMutex sync.Mutex\n\n\tlastActivity time.Time\n\t// For diagnostics only.\n\tstartTime   time.Time\n\trequestPath string\n\n\t// mark that the connection should be dropped\n\tmarkReap chan struct{}\n}\n\ntype RelayClientUnavailableError struct {\n\tclient string\n}\n\nfunc (e *RelayClientUnavailableError) Error() string {\n\treturn fmt.Sprintf(\"Cannot reach the client %q. Check that it's turned on, set up, and connected to the internet. (unknown client)\", e.client)\n}\n\n// broker implements a thread-safe map for the request and response queues.\n// Requests (req) are mapped by server-name. There is only channel per relay-\n// client (identified by the server query parameter)\n// Responses (resp) are mapped by stream id (randomly generated hex string).\n// There can be multiple concurrent transfers per relay-client, each identified\n// by a unique id query parameter.\ntype broker struct {\n\tm    sync.Mutex\n\treq  map[string]chan *pb.HttpRequest\n\tresp map[string]*pendingResponse\n}\n\nfunc newBroker() *broker {\n\tvar r broker\n\tr.req = make(map[string]chan *pb.HttpRequest)\n\tr.resp = make(map[string]*pendingResponse)\n\treturn &r\n}\n\n// Healthy can be used for server health checks. If the server is deadlocked it\n// will block forever.\nfunc (r *broker) Healthy() error {\n\tr.m.Lock()\n\tdefer r.m.Unlock()\n\treturn nil\n}\n\n// RelayRequest matches a pending relay client's request to the encapsulated\n// request and returns a channel for the results.\nfunc (r *broker) RelayRequest(server string, request *pb.HttpRequest) (<-chan *pb.HttpResponse, error) {\n\tid := *request.Id\n\ttargetUrl, err := url.Parse(*request.Url)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to parse URL: %v\", err)\n\t}\n\n\tr.m.Lock()\n\tif r.req[server] == nil {\n\t\t// If we haven't seen this relay client before, immediately return error.\n\t\tr.m.Unlock()\n\t\treturn nil, &RelayClientUnavailableError{client: server}\n\t}\n\tif r.resp[id] != nil {\n\t\tr.m.Unlock()\n\t\treturn nil, fmt.Errorf(\"Multiple clients trying to handle request ID %s on server %s\", id, server)\n\t}\n\tts := time.Now()\n\tr.resp[id] = &pendingResponse{\n\t\trequestStream:  make(chan []byte),\n\t\tresponseStream: make(chan *pb.HttpResponse),\n\t\tlastActivity:   ts,\n\t\tstartTime:      ts,\n\t\trequestPath:    targetUrl.Path,\n\t\tmarkReap:       make(chan struct{}),\n\t}\n\treqChan := r.req[server]\n\trespChan := r.resp[id].responseStream\n\tr.m.Unlock()\n\n\tslog.Info(\"Enqueuing request\", slog.String(\"ID\", id))\n\tbrokerRequests.WithLabelValues(\"client\", server).Inc()\n\tselect {\n\t// This blocks until we get a free spot in the broker's request channel.\n\tcase reqChan <- request:\n\t\treturn respChan, nil\n\tcase <-time.After(10 * time.Second):\n\t\t// This branch is triggered if the channel is not ready to consume the request\n\t\t// since it is still busy with handling a different request.\n\t\treturn nil, fmt.Errorf(\"Cannot reach the client %q. Check that it's turned on, set up, and connected to the internet. If the network config recently changed, try again in 1-2 minutes. (timeout waiting for relay client to accept request)\", server)\n\t}\n}\n\n// StopRelayRequest forgets a relaying request, this causes the next chunk from the backend\n// with the relay id to not be recognized, resulting in the relay server returning an error.\nfunc (r *broker) StopRelayRequest(requestId string) {\n\tr.m.Lock()\n\tdefer r.m.Unlock()\n\tdelete(r.resp, requestId)\n}\n\n// GetRequest obtains a client's request for the server identifier. It blocks\n// until a client makes a request.\nfunc (r *broker) GetRequest(ctx context.Context, server, path string) (*pb.HttpRequest, error) {\n\tr.m.Lock()\n\tif r.req[server] == nil {\n\t\t// This happens when the relay-server started and a client connects before\n\t\t// the relay-client connected.\n\t\tr.req[server] = make(chan *pb.HttpRequest)\n\t}\n\treqChan := r.req[server]\n\tr.m.Unlock()\n\n\tbrokerRequests.WithLabelValues(\"server_request\", server).Inc()\n\tselect {\n\tcase req := <-reqChan:\n\t\tbrokerResponses.WithLabelValues(\"server_request\", \"ok\", server).Inc()\n\t\treturn req, nil\n\tcase <-time.After(time.Second * 30):\n\t\tbrokerResponses.WithLabelValues(\"server_request\", \"timeout\", server).Inc()\n\t\treturn nil, fmt.Errorf(\"No request received within timeout\")\n\tcase <-ctx.Done():\n\t\treturn nil, fmt.Errorf(\"Server is restarting\")\n\t}\n}\n\n// GetRequestStream gets data from the stream that follows a client's HTTP\n// request. For example, when using `kubectl exec` this passes stdin data from\n// the broker to the relay client.\n// If no ongoing request matches the given ID, this returns ok=false.\nfunc (r *broker) GetRequestStream(id string) ([]byte, bool) {\n\tr.m.Lock()\n\tpr := r.resp[id]\n\tr.m.Unlock()\n\tif pr == nil {\n\t\treturn nil, false\n\t}\n\n\tselect {\n\tcase data := <-pr.requestStream:\n\t\treturn data, true\n\tcase <-time.After(time.Second * 30):\n\t\treturn []byte{}, true\n\t}\n}\n\n// PutsRequestStream adds data from the stream that follows a client's HTTP\n// request. For example, when using `kubectl exec` this passes stdin data from\n// kubectl to the broker.\n// If no ongoing request matches the given ID, this returns ok=false.\nfunc (r *broker) PutRequestStream(id string, data []byte) bool {\n\tr.m.Lock()\n\tpr := r.resp[id]\n\tif pr == nil {\n\t\tr.m.Unlock()\n\t\treturn false\n\t}\n\tpr.requestStreamMutex.Lock()\n\tdefer pr.requestStreamMutex.Unlock()\n\tr.m.Unlock()\n\n\tselect {\n\tcase pr.requestStream <- data:\n\t\tbreak\n\tcase <-pr.markReap:\n\t\tslog.Error(\"Error sending user client request to backend (Closed due to inactivity)\", slog.String(\"ID\", id))\n\t\treturn true\n\t}\n\treturn true\n}\n\n// SendResponse delivers the HttpResponse to the user-client handler that created the\n// request. It fails if and only if the request ID is not recognized.\nfunc (r *broker) SendResponse(resp *pb.HttpResponse) error {\n\tid := *resp.Id\n\tbackendName := strings.SplitN(id, \":\", 2)[0]\n\tr.m.Lock()\n\tpr := r.resp[id]\n\tif pr == nil {\n\t\tr.m.Unlock()\n\t\tbrokerResponses.WithLabelValues(\"server_response\", \"not recognized or reaches the inactivity timeout\", backendName).Inc()\n\t\treturn fmt.Errorf(\"Duplicate or invalid request ID %s\", id)\n\t}\n\t// hold `sendMutex` throughout the function to ensure that `responseStream` is not closed\n\t// while we are writing to it. We must acquire the lock while we are holding `r.m` to\n\t// avoid `ReapInactiveRequests` closing `responseStream` between the time that we\n\t// release `r.m` and lock `pr.sendMutex`.\n\tpr.sendMutex.Lock()\n\tdefer pr.sendMutex.Unlock()\n\tif resp.GetEof() {\n\t\t// remove this request from the broker to prevent `ReapInactiveRequests` from processing (and closing `pr.responseStream`)\n\t\t// a request that is about to be closed.\n\t\tdelete(r.resp, id)\n\t} else {\n\t\tpr.lastActivity = time.Now()\n\t}\n\tduration := time.Since(pr.startTime).Seconds()\n\t// Release the lock on the broker before we write to `responseStream` so it does not\n\t// block other requests.\n\tr.m.Unlock()\n\n\tselect {\n\t// Writing to this channel will notify consumers which are waiting for data\n\t// on the channel returned by RelayRequest(). Note that the rate that we can write\n\t// is limited by the rate that the user client consumes the stream.\n\tcase pr.responseStream <- resp:\n\t\tbreak\n\tcase <-pr.markReap:\n\t\treturn fmt.Errorf(\"Closed due to inactivity\")\n\t}\n\n\tbrokerRequests.WithLabelValues(\"server_response\", backendName).Inc()\n\tbrokerResponseDurations.WithLabelValues(\"server_response\", backendName).Observe(duration)\n\tif resp.GetEof() {\n\t\t// this request is already removed from the broker earlier so `ReapInactiveRequests` will not\n\t\t// process this and attempt to close the channel twice.\n\t\tclose(pr.responseStream)\n\t\tbackendDuration := (time.Duration(resp.GetBackendDurationMs()) * time.Millisecond).Seconds()\n\t\tif backendDuration > 0.0 {\n\t\t\tbrokerBackendResponseDurations.WithLabelValues(\"server_response\", backendName).Observe(backendDuration)\n\t\t\tbrokerOverheadDurations.WithLabelValues(\"server_response\", backendName).Observe(duration - backendDuration)\n\t\t}\n\t\tslog.Info(\"Delivered final response to client\", slog.String(\"ID\", id), slog.Int(\"Bytes\", len(resp.Body)), slog.Float64(\"Elapsed\", duration), slog.Float64(\"BackendDuration\", backendDuration))\n\t} else {\n\t\tslog.Info(\"Delivered response to client\", slog.String(\"ID\", id), slog.Int(\"Bytes\", len(resp.Body)), slog.Float64(\"Elapsed\", duration))\n\t}\n\tbrokerResponses.WithLabelValues(\"server_response\", \"ok\", backendName).Inc()\n\treturn nil\n}\n\nfunc (r *broker) ReapInactiveRequests(threshold time.Time) {\n\tr.m.Lock()\n\tfor id, pr := range r.resp {\n\t\tif pr.lastActivity.Before(threshold) {\n\t\t\tslog.Info(\"Timeout on inactive request\", slog.String(\"ID\", id))\n\t\t\t// Closing `pr.markReap` tells `SendResponse` and `PutRequestStream` to stop.\n\t\t\tclose(pr.markReap)\n\n\t\t\tpr.requestStreamMutex.Lock()\n\t\t\tclose(pr.requestStream)\n\t\t\tpr.requestStreamMutex.Unlock()\n\n\t\t\t// If we block on this lock, it means `SendResponse` is writing to the channel, since we just closed\n\t\t\t// `markReap`, it should release the lock soon and we can safely close the channel.\n\t\t\tpr.sendMutex.Lock()\n\t\t\tclose(pr.responseStream)\n\t\t\tpr.sendMutex.Unlock()\n\n\t\t\t// Amazingly, this is safe in Go: https://stackoverflow.com/questions/23229975/is-it-safe-to-remove-selected-keys-from-map-within-a-range-loop\n\t\t\tdelete(r.resp, id)\n\t\t}\n\t}\n\tr.m.Unlock()\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/server/broker_test.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tpb \"github.com/googlecloudrobotics/core/src/proto/http-relay\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst (\n\t// These IDs are used for testing multiple requests. The contents of the\n\t// strings are not important, as long as they are unique.\n\tidOne     = \"idOne\"\n\tidTwo     = \"idTwo\"\n\tidThree   = \"idThree\"\n\tunknownID = \"unknownID\"\n)\n\n// brokerConn helps manage send/receive operations on the broker. Because the broker\n// will immediately reject user clients for a backend which it has not seen before,\n// we need to ensure that the backend side makes the connection first.\n//\n// Only one request id can be used for each `brokerConn`\ntype brokerConn struct {\n\tready chan struct{}\n}\n\nfunc newBrokerConn() brokerConn {\n\treturn brokerConn{\n\t\tready: make(chan struct{}),\n\t}\n}\n\nfunc (bt *brokerConn) runSender(t *testing.T, b *broker, s string, m string, wg *sync.WaitGroup) {\n\t<-bt.ready\n\trespChan, err := b.RelayRequest(s, &pb.HttpRequest{Id: proto.String(m), Url: proto.String(\"http://example.com/foo\")})\n\tif err != nil {\n\t\tt.Errorf(\"Got relay request error: %v\", err)\n\t}\n\tresp, more := <-respChan\n\tif want, got := m, string(resp.Body); want != got {\n\t\tt.Errorf(\"Wrong response; want %s; got %s\\n\", want, got)\n\t}\n\tresp, more = <-respChan\n\tif more {\n\t\tt.Errorf(\"Got more than 1 response: %v\", resp)\n\t}\n\twg.Done()\n}\n\nfunc (bt *brokerConn) bakeRequest(b *broker, s string) {\n\t// ensure that the request is created before the user client connects\n\tb.m.Lock()\n\tif _, found := b.req[s]; !found {\n\t\tb.req[s] = make(chan *pb.HttpRequest)\n\t}\n\tb.m.Unlock()\n\tclose(bt.ready)\n}\n\nfunc (bt *brokerConn) runReceiver(t *testing.T, b *broker, s string, wg *sync.WaitGroup) {\n\tbt.bakeRequest(b, s)\n\treq, err := b.GetRequest(context.Background(), s, \"/\")\n\tif err != nil {\n\t\tt.Errorf(\"Error when getting request: %v\", err)\n\t}\n\terr = b.SendResponse(&pb.HttpResponse{Id: req.Id, Body: []byte(*req.Id), Eof: proto.Bool(true)})\n\tif err != nil {\n\t\tt.Errorf(\"Error when sending response: %v\", err)\n\t}\n\twg.Done()\n}\n\n// runSenderStream expects two items in the response stream, and it doesn't care about the contents.\nfunc (bt *brokerConn) runSenderStream(t *testing.T, b *broker, s string, m string, wg *sync.WaitGroup) {\n\t<-bt.ready\n\trespChan, err := b.RelayRequest(s, &pb.HttpRequest{Id: proto.String(m), Url: proto.String(\"http://example.com/foo\")})\n\tif err != nil {\n\t\tt.Errorf(\"Got relay request error: %v\", err)\n\t}\n\t// First response (Eof=false)\n\tif _, more := <-respChan; !more {\n\t\tt.Errorf(\"Got zero responses, want two.\")\n\t}\n\t// Second response (Eof=true)\n\tif _, more := <-respChan; !more {\n\t\tt.Errorf(\"Got one response, want two.\")\n\t}\n\t// Check that channel is closed.\n\tif _, more := <-respChan; more {\n\t\tt.Errorf(\"Got more than two responses, want two.\")\n\t}\n\twg.Done()\n}\n\n// runReceiverStream sends two items in the response stream, waiting before the second.\n// It returns after the first response has been sent.\nfunc (bt *brokerConn) runReceiverStream(t *testing.T, b *broker, s string, wg *sync.WaitGroup, done <-chan bool) {\n\tbt.bakeRequest(b, s)\n\treq, err := b.GetRequest(context.Background(), s, \"/\")\n\tif err != nil {\n\t\tt.Errorf(\"Error when getting request: %v\", err)\n\t}\n\terr = b.SendResponse(&pb.HttpResponse{Id: req.Id, Body: []byte(*req.Id), Eof: proto.Bool(false)})\n\tif err != nil {\n\t\tt.Errorf(\"Error when sending response: %v\", err)\n\t}\n\tgo func() {\n\t\t<-done\n\t\terr = b.SendResponse(&pb.HttpResponse{Id: req.Id, Body: []byte(*req.Id), Eof: proto.Bool(true)})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error when sending response: %v\", err)\n\t\t}\n\t\twg.Done()\n\t}()\n}\n\nfunc TestNormalCase(t *testing.T) {\n\tb := newBroker()\n\tvar bConns [3]brokerConn\n\tfor i := range bConns {\n\t\tbConns[i] = newBrokerConn()\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(6)\n\tgo bConns[0].runSender(t, b, \"foo\", idOne, &wg)\n\tgo bConns[1].runSender(t, b, \"foo\", idTwo, &wg)\n\tgo bConns[2].runSender(t, b, \"bar\", idThree, &wg)\n\tgo bConns[0].runReceiver(t, b, \"foo\", &wg)\n\tgo bConns[1].runReceiver(t, b, \"foo\", &wg)\n\tgo bConns[2].runReceiver(t, b, \"bar\", &wg)\n\twg.Wait()\n}\n\nfunc TestResponseStream(t *testing.T) {\n\tb := newBroker()\n\tbc := newBrokerConn()\n\tvar wg sync.WaitGroup\n\tdone := make(chan bool)\n\twg.Add(2)\n\tgo bc.runSenderStream(t, b, \"foo\", idOne, &wg)\n\tbc.runReceiverStream(t, b, \"foo\", &wg, done)\n\tdone <- true\n\twg.Wait()\n}\n\nfunc TestMissingId(t *testing.T) {\n\tb := newBroker()\n\terr := b.SendResponse(&pb.HttpResponse{Id: proto.String(idOne)})\n\tif err == nil {\n\t\tt.Errorf(\"Invalid response did not produce an error\")\n\t}\n}\n\nfunc TestDuplicateId(t *testing.T) {\n\tb := newBroker()\n\tbc := newBrokerConn()\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo bc.runSender(t, b, \"foo\", idOne, &wg)\n\tgo bc.runReceiver(t, b, \"foo\", &wg)\n\twg.Wait()\n\n\terr := b.SendResponse(&pb.HttpResponse{Id: proto.String(idOne)})\n\tif err == nil {\n\t\tt.Errorf(\"Duplicate response did not produce an error\")\n\t}\n}\n\nfunc TestRequestStream(t *testing.T) {\n\t// Start a request that won't terminate until we send `done`.\n\tb := newBroker()\n\tbc := newBrokerConn()\n\tvar wg sync.WaitGroup\n\tdone := make(chan bool)\n\twg.Add(2)\n\tgo bc.runSenderStream(t, b, \"foo\", idOne, &wg)\n\tbc.runReceiverStream(t, b, \"foo\", &wg, done)\n\n\t// Send a message over the request stream and assert that it arrives.\n\tgo func() {\n\t\tok := b.PutRequestStream(idOne, []byte(\"hello\"))\n\t\tif !ok {\n\t\t\tt.Error(\"PutRequestStream(idOne, \\\"hello\\\") = false, want true\")\n\t\t}\n\t}()\n\tdata, ok := b.GetRequestStream(idOne)\n\tif !ok {\n\t\tt.Error(\"data, ok := GetRequestStream(idOne); ok = false, want true\")\n\t}\n\tif !bytes.Equal(data, []byte(\"hello\")) {\n\t\tt.Errorf(\"data, ok := GetRequestStream(idOne); data = %q, want \\\"hello\\\"\", data)\n\t}\n\n\t// Complete the ongoing request.\n\tdone <- true\n\twg.Wait()\n}\n\nfunc TestRequestStreamUnknownID(t *testing.T) {\n\tb := newBroker()\n\tif ok := b.PutRequestStream(unknownID, []byte{}); ok {\n\t\tt.Error(\"ok := PutRequestStream(unknownID, \\\"\\\"); ok = true, want false\")\n\t}\n\tif _, ok := b.GetRequestStream(unknownID); ok {\n\t\tt.Error(\"_, ok := GetRequestStream(unknownID; ok = true, want false\")\n\t}\n}\n\nfunc TestTimeout(t *testing.T) {\n\tb := newBroker()\n\t// create the request channel manually to avoid race condition between the 2\n\t// goroutines below\n\tb.req[\"foo\"] = make(chan *pb.HttpRequest)\n\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo func() {\n\t\trespChan, err := b.RelayRequest(\"foo\", &pb.HttpRequest{Id: proto.String(idOne), Url: proto.String(\"http://example.com/foo\")})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Got relay request error: %v\", err)\n\t\t}\n\t\tif _, more := <-respChan; more {\n\t\t\tt.Errorf(\"Got unexpected response\")\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tslog.Info(\"Getting request\")\n\t\tb.GetRequest(context.Background(), \"foo\", \"/\")\n\t\tslog.Info(\"Reaping inactive requests\")\n\t\tb.ReapInactiveRequests(time.Now().Add(10 * time.Second))\n\t\tslog.Info(\"Done\")\n\t\twg.Done()\n\t}()\n\twg.Wait()\n}\n\nfunc TestReapWhileSendingResponse(t *testing.T) {\n\tb := newBroker()\n\tb.req[\"foo\"] = make(chan *pb.HttpRequest)\n\n\t// create a broker connection between the user client and backend side, but don't\n\t// start sending data. \"req\" is backend side and \"resp\" is user client side.\n\tvar req *pb.HttpRequest\n\tvar reqErr error\n\t// var resp <-chan *pb.HttpResponse\n\tvar respErr error\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo func() {\n\t\treq, reqErr = b.GetRequest(context.Background(), \"foo\", \"/\")\n\t\tif reqErr != nil {\n\t\t\tt.Errorf(\"GetRequest error:\", reqErr)\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\t_, respErr = b.RelayRequest(\"foo\", &pb.HttpRequest{Id: proto.String(idOne), Url: proto.String(\"http://example.com/foo\")})\n\t\tif respErr != nil {\n\t\t\tt.Errorf(\"RelayRequest error:\", respErr)\n\t\t}\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\tif reqErr != nil || respErr != nil {\n\t\tt.Errorf(\"Error making broker connection\")\n\t}\n\n\t// start sending response to user client, BUT do not start consuming the response.\n\twg.Add(1)\n\tgo func() {\n\t\treqErr = b.SendResponse(&pb.HttpResponse{Id: req.Id, Body: []byte(*req.Id), Eof: proto.Bool(false)})\n\t\tif reqErr == nil || reqErr.Error() != \"Closed due to inactivity\" {\n\t\t\tt.Errorf(\"Wrong SendResponse error or no error:\", reqErr)\n\t\t}\n\t\twg.Done()\n\t}()\n\t// FIXME(koonpeng): we need to wait for the goroutinue to be blocked on writing the response, currently\n\t// there is no way to confirm that so we use a sleep.\n\ttime.Sleep(100 * time.Millisecond)\n\t// reap the request\n\tb.ReapInactiveRequests(time.Now().Add(10 * time.Second))\n\twg.Wait()\n}\n\nfunc TestReapWhileSendingRequest(t *testing.T) {\n\tb := newBroker()\n\tb.req[\"foo\"] = make(chan *pb.HttpRequest)\n\n\t// create a broker connection between the user client and backend side, but don't\n\t// start sending data. \"req\" is backend side and \"resp\" is user client side.\n\tvar req *pb.HttpRequest\n\tvar reqErr error\n\t// var resp <-chan *pb.HttpResponse\n\tvar respErr error\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo func() {\n\t\treq, reqErr = b.GetRequest(context.Background(), \"foo\", \"/\")\n\t\tif reqErr != nil {\n\t\t\tt.Errorf(\"GetRequest error:\", reqErr)\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\t_, respErr = b.RelayRequest(\"foo\", &pb.HttpRequest{Id: proto.String(idOne), Url: proto.String(\"http://example.com/foo\")})\n\t\tif respErr != nil {\n\t\t\tt.Errorf(\"RelayRequest error:\", respErr)\n\t\t}\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\tif reqErr != nil || respErr != nil {\n\t\tt.Errorf(\"Error making broker connection\")\n\t}\n\n\t// start sending response to backend, BUT do not start consuming on the backend side\n\twg.Add(1)\n\tgo func() {\n\t\tif ok := b.PutRequestStream(*req.Id, []byte(*req.Id)); !ok {\n\t\t\tt.Errorf(\"Error putting request stream\")\n\t\t}\n\t\twg.Done()\n\t}()\n\t// FIXME(koonpeng): we need to wait for the goroutinue to be blocked on writing the request, currently\n\t// there is no way to confirm that so we use a sleep.\n\ttime.Sleep(100 * time.Millisecond)\n\t// reap the request\n\tb.ReapInactiveRequests(time.Now().Add(10 * time.Second))\n\twg.Wait()\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/server/server.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\tpb \"github.com/googlecloudrobotics/core/src/proto/http-relay\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"go.opencensus.io/plugin/ochttp\"\n\t\"go.opencensus.io/plugin/ochttp/propagation/tracecontext\"\n\t\"go.opencensus.io/trace\"\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nconst (\n\tclientPrefix = \"/client/\"\n\t// Time to wait for requests to complete before calling panic(). This should\n\t// be less that the kubelet's timeout (30s by default) so that we can print\n\t// a stack trace and debug what is still running.\n\tcleanShutdownTimeout = 20 * time.Second\n\t// Print more detailed logs when enabled.\n\tdebugLogs = false\n\n\t// DefaultPort is the default port number to listen on.\n\tDefaultPort = 80\n\t// DefaultBlockSize is the default size of i/o buffer in bytes.\n\tDefaultBlockSize = 10 * 1024\n\t// DefaultInactiveRequestTimeout is the default timeout for inactive requests. In particular, this sets a limit on how long the backend can wait before writing headers and the response status.\n\tDefaultInactiveRequestTimeout = 60 * time.Second\n)\n\ntype Config struct {\n\t// Port number to listen on.\n\tPort int\n\t// BlockSize is the size of i/o buffer in bytes.\n\tBlockSize int\n\t// InactiveRequestTimeout is the timeout for inactive requests.\n\tInactiveRequestTimeout time.Duration\n}\n\ntype Server struct {\n\tconf Config\n\tb    *broker\n}\n\nfunc NewServer(conf Config) *Server {\n\tif conf.Port == -1 { // let tests pass a value of 0 to let the os pick a port\n\t\tconf.Port = DefaultPort\n\t}\n\tif conf.BlockSize == 0 {\n\t\tconf.BlockSize = DefaultBlockSize\n\t}\n\tif conf.InactiveRequestTimeout == 0 {\n\t\tconf.InactiveRequestTimeout = DefaultInactiveRequestTimeout\n\t}\n\ts := &Server{\n\t\tconf: conf,\n\t\tb:    newBroker(),\n\t}\n\tgo func() {\n\t\tfor t := range time.Tick(10 * time.Second) {\n\t\t\ts.b.ReapInactiveRequests(t.Add(-1 * conf.InactiveRequestTimeout))\n\t\t}\n\t}()\n\treturn s\n}\n\nfunc createId() string {\n\tu := make([]byte, 16)\n\t// err is documented as always nil\n\trand.Read(u)\n\treturn hex.EncodeToString(u)\n}\n\nfunc marshalHeader(h *http.Header) []*pb.HttpHeader {\n\tr := []*pb.HttpHeader{}\n\tfor k, vs := range *h {\n\t\tfor _, v := range vs {\n\t\t\tr = append(r, &pb.HttpHeader{Name: proto.String(k), Value: proto.String(v)})\n\t\t}\n\t}\n\treturn r\n}\n\nfunc unmarshalHeader(w http.ResponseWriter, protoHeader []*pb.HttpHeader) {\n\tfor _, h := range protoHeader {\n\t\tw.Header().Add(*h.Name, *h.Value)\n\t}\n}\n\nfunc addServiceName(span *trace.Span) {\n\trelayServerAttr := trace.StringAttribute(\"service.name\", \"http-relay-server\")\n\tspan.AddAttributes(relayServerAttr)\n}\n\nfunc extractBackendNameAndPath(r *http.Request) (backendName string, path string, err error) {\n\tif strings.HasPrefix(r.URL.Path, clientPrefix) {\n\t\t// After stripping, the path is \"${SERVER_NAME}/${REQUEST}\"\n\t\tpathParts := strings.SplitN(strings.TrimPrefix(r.URL.Path, clientPrefix), \"/\", 2)\n\t\tbackendName = pathParts[0]\n\t\tif backendName == \"\" {\n\t\t\terr = fmt.Errorf(\"Request path too short: missing remote server identifier\")\n\t\t\treturn\n\t\t}\n\t\tpath = \"/\"\n\t\tif len(pathParts) > 1 {\n\t\t\tpath += pathParts[1]\n\t\t}\n\t} else {\n\t\t// Requests without the /client/ prefix are gRPC requests. The backend is\n\t\t// identified by \"X-Server-Name\" header.\n\t\theaders, ok := r.Header[\"X-Server-Name\"]\n\t\tif !ok {\n\t\t\terr = fmt.Errorf(\"Request without required header: \\\"X-Server-Name\\\"\")\n\t\t\treturn\n\t\t}\n\t\tbackendName = headers[0]\n\t\tpath = r.URL.Path\n\t}\n\treturn\n}\n\ntype responseChunk struct {\n\tBody     []byte\n\tTrailers []*pb.HttpHeader\n}\n\n// responseFilter enforces that there's at least one HttpResponse in the 'in'\n// channel and that the first response has a status code. It collects the\n// responses and then returns headers and status-code. Additionally, it\n// returns body and trailers asynchronously via the returned channel.\nfunc (s *Server) responseFilter(backendCtx backendContext, in <-chan *pb.HttpResponse) ([]*pb.HttpHeader, int, <-chan *responseChunk) {\n\tresponseChunks := make(chan *responseChunk, 1)\n\tfirstMessage, more := <-in\n\tif !more {\n\t\tbrokerResponses.WithLabelValues(\"client\", \"missing_message\", backendCtx.ServerName).Inc()\n\t\tresponseChunks <- &responseChunk{\n\t\t\tBody: []byte(fmt.Sprintf(\"Timeout after %v, indicating that the backend request took too long\", s.conf.InactiveRequestTimeout)),\n\t\t}\n\t\tclose(responseChunks)\n\t\treturn nil, http.StatusGatewayTimeout, responseChunks\n\t}\n\tif firstMessage.StatusCode == nil {\n\t\tbrokerResponses.WithLabelValues(\"client\", \"missing_header\", backendCtx.ServerName).Inc()\n\t\tresponseChunks <- &responseChunk{\n\t\t\tBody: []byte(\"Received no header from relay client\"),\n\t\t}\n\t\tclose(responseChunks)\n\t\t// Flush remaining messages\n\t\tfor range in {\n\t\t}\n\t\treturn nil, http.StatusInternalServerError, responseChunks\n\t}\n\n\tresponseChunks <- &responseChunk{\n\t\tBody:     []byte(firstMessage.Body),\n\t\tTrailers: []*pb.HttpHeader(firstMessage.Trailer),\n\t}\n\n\tgo func() {\n\t\tfor backendResp := range in {\n\t\t\tbrokerResponses.WithLabelValues(\"client\", \"ok\", backendCtx.ServerName).Inc()\n\t\t\tresponseChunks <- &responseChunk{\n\t\t\t\tBody:     []byte(backendResp.Body),\n\t\t\t\tTrailers: []*pb.HttpHeader(backendResp.Trailer),\n\t\t\t}\n\t\t}\n\t\tclose(responseChunks)\n\t}()\n\treturn firstMessage.Header, int(*firstMessage.StatusCode), responseChunks\n}\n\ntype backendContext struct {\n\tId         string\n\tServerName string\n\tPath       string\n}\n\nfunc newBackendContext(r *http.Request) (*backendContext, error) {\n\tserverName, path, err := extractBackendNameAndPath(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &backendContext{\n\t\tId:         serverName + \":\" + createId(),\n\t\tServerName: serverName,\n\t\tPath:       path,\n\t}, nil\n}\n\nfunc (s *Server) health(w http.ResponseWriter, r *http.Request) {\n\ttimer := time.AfterFunc(8*time.Second, func() {\n\t\tslog.Warn(\"While checking server health, unable to acquire lock after 8 seconds\")\n\t})\n\tdefer timer.Stop()\n\n\tif err := s.b.Healthy(); err != nil {\n\t\tslog.Error(\"Health check failed\", ilog.Err(err))\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", \"text/plain\")\n\tw.Write([]byte(\"ok\"))\n}\n\n// bidirectionalStream handles a 101 Switching Protocols response from the\n// backend, by \"hijacking\" to get a bidirectional connection to the client,\n// and streaming data between client and broker/relay client.\nfunc (s *Server) bidirectionalStream(backendCtx backendContext, w http.ResponseWriter, responseChunks <-chan *responseChunk) {\n\thj, ok := w.(http.Hijacker)\n\tif !ok {\n\t\thttp.Error(w, \"Backend returned 101 Switching Protocols, which is not supported by the relay server\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.WriteHeader(http.StatusSwitchingProtocols)\n\tconn, bufrw, err := hj.Hijack()\n\tif err != nil {\n\t\t// After a failed hijack, the connection is in an unknown state and\n\t\t// we can't report an error to the client.\n\t\tslog.Error(\"Failed to hijack connection after 101\", slog.String(\"ID\", backendCtx.Id), ilog.Err(err))\n\t\treturn\n\t}\n\tslog.Info(\"Switched protocols\", slog.String(\"ID\", backendCtx.Id))\n\tdefer conn.Close()\n\n\tgo func() {\n\t\t// This goroutine handles the request stream from client to backend.\n\t\tslog.Info(\"Trying to read from bidi-stream\", slog.String(\"ID\", backendCtx.Id))\n\t\tfor {\n\t\t\t// This must be a new buffer each time, as the channel is not making a copy\n\t\t\tbytes := make([]byte, s.conf.BlockSize)\n\t\t\t// Here we get the client stream (e.g. kubectl or k9s)\n\t\t\tn, err := bufrw.Read(bytes)\n\t\t\tif err != nil {\n\t\t\t\t// TODO(https://github.com/golang/go/issues/4373): in Go 1.13,\n\t\t\t\t// we may be able to suppress the \"read from closed connection\" better.\n\t\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\t\t// Request ended and connection closed by HTTP server.\n\t\t\t\t\tslog.Info(\"End of bidi-stream stream (closed socket)\", slog.String(\"ID\", backendCtx.Id))\n\t\t\t\t} else {\n\t\t\t\t\t// Connection has unexpectedly failed for some other reason.\n\t\t\t\t\tslog.Error(\"Error reading from bidi-stream\", slog.String(\"ID\", backendCtx.Id), ilog.Err(err))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tslog.Info(\"Read from bidi-stream\", slog.String(\"ID\", backendCtx.Id), slog.Int(\"Bytes\", n))\n\t\t\tif ok = s.b.PutRequestStream(backendCtx.Id, bytes[:n]); !ok {\n\t\t\t\tslog.Info(\"End of bidi-stream stream\", slog.String(\"ID\", backendCtx.Id))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tslog.Info(\"Uploaded from bidi-stream\", slog.String(\"ID\", backendCtx.Id), slog.Int(\"Bytes\", n))\n\t\t}\n\t}()\n\n\tnumBytes := 0\n\tfor responseChunk := range responseChunks {\n\t\tif _, err = bufrw.Write(responseChunk.Body); err != nil {\n\t\t\tslog.Error(\"Error writing response to bidi-stream\", slog.String(\"ID\", backendCtx.Id), ilog.Err(err))\n\t\t\treturn\n\t\t}\n\t\tbufrw.Flush()\n\t\tnumBytes += len(responseChunk.Body)\n\t}\n\tslog.Info(\"Wrote response chunk to bidi-stream\", slog.String(\"ID\", backendCtx.Id), slog.Int(\"Bytes\", numBytes))\n}\n\nfunc (s *Server) readRequestBody(ctx context.Context, r *http.Request) ([]byte, error) {\n\t_, span := trace.StartSpan(ctx, \"Read request body\")\n\taddServiceName(span)\n\tdefer span.End()\n\treturn io.ReadAll(r.Body)\n}\n\nfunc (s *Server) createBackendRequest(backendCtx backendContext, r *http.Request, body []byte) *pb.HttpRequest {\n\tbackendUrl := url.URL{\n\t\tScheme:   \"http\",\n\t\tHost:     \"invalid\",\n\t\tPath:     backendCtx.Path,\n\t\tRawQuery: r.URL.RawQuery,\n\t\tFragment: r.URL.Fragment,\n\t}\n\n\tbackendReq := &pb.HttpRequest{\n\t\tId:     proto.String(backendCtx.Id),\n\t\tMethod: proto.String(r.Method),\n\t\tHost:   proto.String(r.Host),\n\t\tUrl:    proto.String(backendUrl.String()),\n\t\tHeader: marshalHeader(&r.Header),\n\t\tBody:   body,\n\t}\n\n\treturn backendReq\n}\n\nfunc (s *Server) relayRequest(ctx context.Context, backendCtx backendContext, request *pb.HttpRequest) (<-chan *pb.HttpResponse, error) {\n\t_, span := trace.StartSpan(ctx, \"Schedule request for pickup\")\n\taddServiceName(span)\n\tdefer span.End()\n\n\tbackendRespChan, err := s.b.RelayRequest(backendCtx.ServerName, request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn backendRespChan, nil\n}\n\nfunc (s *Server) waitForFirstResponseAndHandleSwitching(ctx context.Context, backendCtx backendContext, w http.ResponseWriter, backendRespChan <-chan *pb.HttpResponse) ([]*pb.HttpHeader, <-chan *responseChunk, bool) {\n\t_, span := trace.StartSpan(ctx, \"Waiting for first response\")\n\taddServiceName(span)\n\tdefer span.End()\n\n\theader, status, responseChunksChan := s.responseFilter(backendCtx, backendRespChan)\n\tif header != nil {\n\t\tunmarshalHeader(w, header)\n\t}\n\n\tif status == http.StatusSwitchingProtocols {\n\t\tspan.AddAttributes(trace.StringAttribute(\"notes\", \"Received 101 switching protocols.\"))\n\t\t// Note: call s.bidirectionalStream before w.WriteHeader so that\n\t\t// bidirectionalStream can set the status on error.\n\t\t// TODO(haukeheibel): I don't get this comment. We never write the\n\t\t// header and just return.\n\t\ts.bidirectionalStream(backendCtx, w, responseChunksChan)\n\t\treturn nil, nil, true\n\t}\n\n\tw.WriteHeader(status)\n\n\treturn header, responseChunksChan, false\n}\n\n// This function is used to handle requests by the user-client.\n// This is e.g. a browser request.\nfunc (s *Server) userClientRequest(w http.ResponseWriter, r *http.Request) {\n\tf := &tracecontext.HTTPFormat{}\n\tvar span *trace.Span\n\tctx := r.Context()\n\tif sctx, ok := f.SpanContextFromRequest(r); ok {\n\t\tctx, span = trace.StartSpanWithRemoteParent(ctx, \"Received user client request \"+r.URL.Path, sctx)\n\t} else {\n\t\tctx, span = trace.StartSpan(ctx, \"Received user client request \"+r.URL.Path)\n\t}\n\taddServiceName(span)\n\t// Embedding the span in the request ensures that the server side spans are correctly\n\t// nested.\n\t// Note: We are overwriting the previous one with the current one which is not a\n\t// problem since we have already read the previous one.\n\tf.SpanContextToRequest(span.SpanContext(), r)\n\t// We can actually defer span.end() since this function will wait until a response from\n\t// a server is being received.\n\tdefer span.End()\n\n\tif debugLogs {\n\t\tdump, _ := httputil.DumpRequest(r, false)\n\t\tslog.Info(\"Received user client request\", slog.String(\"HttpRequest\", string(dump)))\n\t}\n\n\tbackendCtx, err := newBackendContext(r)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tbody, err := s.readRequestBody(ctx, r)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tbackendReq := s.createBackendRequest(*backendCtx, r, body)\n\n\t// Pipe a request into the request channel to it get polled by the relay client.\n\t// Then return the response channel, so we can pass it on and wait on a response\n\t// from the relay-client.\n\tbackendRespChan, err := s.relayRequest(ctx, *backendCtx, backendReq)\n\tif err != nil {\n\t\tif _, ok := err.(*RelayClientUnavailableError); ok {\n\t\t\tw.WriteHeader(http.StatusServiceUnavailable)\n\t\t\tw.Header().Set(\"X-CLOUDROBOTICS-HTTP-RELAY\", backendCtx.Id)\n\t\t\tw.Write([]byte(err.Error()))\n\t\t\treturn\n\t\t}\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer s.b.StopRelayRequest(backendCtx.Id)\n\n\theader, responseChunksChan, done := s.waitForFirstResponseAndHandleSwitching(ctx, *backendCtx, w, backendRespChan)\n\tif done {\n\t\treturn\n\t}\n\n\t_, forwardingResponseSpan := trace.StartSpan(ctx, \"Forwarding backend response to user-client\")\n\taddServiceName(forwardingResponseSpan)\n\tdefer forwardingResponseSpan.End()\n\n\t// This code here will block until we have actually received a response from the backend,\n\t// i.e. this will block until\n\tnumBytes := 0\n\tfor responseChunk := range responseChunksChan {\n\t\tif _, err = w.Write(responseChunk.Body); err != nil {\n\t\t\tslog.Error(\"Error writing response to user-client\", slog.String(\"ID\", backendCtx.Id), ilog.Err(err))\n\t\t\treturn\n\t\t}\n\t\tif flush, ok := w.(http.Flusher); ok {\n\t\t\tflush.Flush()\n\t\t}\n\t\tnumBytes += len(responseChunk.Body)\n\n\t\t// Only the last chunk will actually contain trailers.\n\t\tfor _, h := range responseChunk.Trailers {\n\t\t\tw.Header().Add(http.TrailerPrefix+*h.Name, *h.Value)\n\t\t\tslog.Info(\"Adding real trailer\", slog.String(\"ID\", backendCtx.Id), slog.String(\"Name\", *h.Name), slog.String(\"Value\", *h.Value))\n\t\t}\n\t}\n\n\t// TODO(ensonic): open questions:\n\t// - can we do this less hacky? (see unmarshalHeader() above)\n\t// - why do we not always get them as trailers?\n\tfor _, h := range header {\n\t\tif strings.HasPrefix(*h.Name, \"Grpc-\") {\n\t\t\tw.Header().Add(http.TrailerPrefix+*h.Name, *h.Value)\n\t\t\tslog.Info(\"Adding trailer from header\", slog.String(\"ID\", backendCtx.Id), slog.String(\"Name\", *h.Name), slog.String(\"Value\", *h.Value))\n\t\t}\n\t}\n\n\tslog.Info(\"Wrote response chunk to request\", slog.String(\"ID\", backendCtx.Id), slog.Int(\"Bytes\", numBytes))\n}\n\n// relay-client pulls a request\nfunc (s *Server) serverRequest(w http.ResponseWriter, r *http.Request) {\n\tserver := r.URL.Query().Get(\"server\")\n\tif server == \"\" {\n\t\thttp.Error(w, \"Missing server query parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tslog.Info(\"Relay client connected\", slog.String(\"ServerName\", server))\n\n\t// Get pending request from client and sent as a reply to the relay-client.\n\trequest, err := s.b.GetRequest(r.Context(), server, r.URL.Path)\n\tif err != nil {\n\t\t// Expected if the relay is idle, or if the server is restarting.\n\t\tslog.Debug(\"Relay client got no request\", slog.String(\"ID\", server), ilog.Err(err))\n\t\thttp.Error(w, err.Error(), http.StatusRequestTimeout)\n\t\treturn\n\t}\n\n\tbody, err := proto.Marshal(request)\n\tif err != nil {\n\t\tslog.Error(\"Failed to marshal request\", slog.String(\"ID\", *request.Id), ilog.Err(err))\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/vnd.google.protobuf;proto=cloudrobotics.http_relay.v1alpha1.HttpRequest\")\n\tw.Write(body)\n\tslog.Info(\"Relay client accepted request\", slog.String(\"ID\", *request.Id))\n}\n\nfunc (s *Server) serverRequestStream(w http.ResponseWriter, r *http.Request) {\n\tid := r.URL.Query().Get(\"id\")\n\tif id == \"\" {\n\t\thttp.Error(w, \"Missing id query parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tdata, ok := s.b.GetRequestStream(id)\n\tif !ok {\n\t\t// Using the 410 Gone error tells the relay client that this request\n\t\t// has completed.\n\t\thttp.Error(w, \"No ongoing request with id \"+id, http.StatusGone)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/octet-data\")\n\tw.Write(data)\n\tslog.Info(\"Relay client pulled streamed request chunk\", slog.String(\"ID\", id), slog.Int(\"Bytes\", len(data)))\n}\n\n// This function receives the response from the relay-client after it processed\n// the initial request in the backend.\n// The response is stored in the response channel through which the data is relayed\n// to the initial requester.\nfunc (s *Server) serverResponse(w http.ResponseWriter, r *http.Request) {\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tbr := &pb.HttpResponse{}\n\tif err = proto.Unmarshal(body, br); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Send the response to the actual user-client using our broker.\n\tif err = s.b.SendResponse(br); err != nil {\n\t\t// SendResponse fails if and only if the request ID is bad.\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\t// Respond to the relay-client and notify on successful propagation of\n\t// the backend response.\n\tw.Header().Set(\"Content-Type\", \"text/plain\")\n\tw.Write([]byte(\"ok\"))\n\n\tslog.Info(\"Relay client sent response\", slog.String(\"ID\", *br.Id))\n}\n\nfunc (s *Server) Start() {\n\th := http.NewServeMux()\n\th.HandleFunc(\"/healthz\", s.health)\n\th.HandleFunc(\"/\", s.userClientRequest)\n\th.HandleFunc(\"/server/request\", s.serverRequest)\n\th.HandleFunc(\"/server/requeststream\", s.serverRequestStream)\n\th.HandleFunc(\"/server/response\", s.serverResponse)\n\th.Handle(\"/metrics\", promhttp.Handler())\n\n\t// This context will be terminated we get SIGTERM from Kubernetes. We need\n\t// some boilerplate to make this terminate the HTTP server and the ongoing\n\t// request contexts. This is based on:\n\t// https://www.rudderstack.com/blog/implementing-graceful-shutdown-in-go/#:~:text=Canceling%20long%20running%20requests\n\tmainCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)\n\tdefer stop()\n\n\th2s := &http2.Server{}\n\th2h := h2c.NewHandler(h, h2s)\n\toch := &ochttp.Handler{\n\t\tHandler: h2h,\n\t}\n\th1s := &http.Server{\n\t\tAddr:    fmt.Sprintf(\":%d\", s.conf.Port),\n\t\tHandler: och,\n\t\tBaseContext: func(l net.Listener) context.Context {\n\t\t\tslog.Info(\"Relay server listening\", slog.Int(\"Port\", l.Addr().(*net.TCPAddr).Port))\n\t\t\treturn mainCtx\n\t\t},\n\t}\n\t// Wait for the server to terminate, either because it failed to create a\n\t// listener, or because we got SIGTERM.\n\tg, gCtx := errgroup.WithContext(mainCtx)\n\tg.Go(func() error {\n\t\tif err := h1s.ListenAndServe(); err != http.ErrServerClosed {\n\t\t\treturn err\n\t\t}\n\t\t// ErrServerClosed follows SIGTERM which is normal when updating the\n\t\t// server.\n\t\treturn nil\n\t})\n\tg.Go(func() error {\n\t\t<-gCtx.Done()\n\t\tctx, cancel := context.WithTimeout(context.Background(), cleanShutdownTimeout)\n\t\tdefer cancel()\n\t\treturn h1s.Shutdown(ctx)\n\t})\n\n\tif err := g.Wait(); err != nil {\n\t\t// SIGTERM indicates either a normal shutdown (eg pod update, node pool\n\t\t// update) or a failed liveness check (eg broker deadlock), we can't\n\t\t// easily tell. We panic to help debugging: if the environment sets\n\t\t// GOTRACEBACK=all they will see stacktraces after the panic.\n\t\tslog.Error(\"Server terminated abnormally\", ilog.Err(err))\n\t\tpanic(\"Server terminated abnormally\")\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/http-relay-server/server/server_test.go",
    "content": "// Copyright 2023 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\tpb \"github.com/googlecloudrobotics/core/src/proto/http-relay\"\n\n\thijacktest \"github.com/getlantern/httptest\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc checkResponse(t *testing.T, resp *http.Response, wantStatus int, wantBody string) {\n\tt.Helper()\n\tif want, got := wantStatus, resp.StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to read body stream\")\n\t}\n\tif want, got := wantBody, string(body); want != got {\n\t\tt.Errorf(\"Wrong body; want %s; got %s\", want, got)\n\t}\n}\n\nfunc TestClientHandler(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/client/foo/bar?a=b#c\", strings.NewReader(\"body\"))\n\treq.Header.Add(\"X-Deadline\", \"now\")\n\trespRecorder := httptest.NewRecorder()\n\tserver := NewServer(Config{})\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() { server.userClientRequest(respRecorder, req); wg.Done() }()\n\trelayRequest, err := server.b.GetRequest(context.Background(), \"foo\", \"/\")\n\tif err != nil {\n\t\tt.Errorf(\"Error when getting request: %v\", err)\n\t}\n\n\twantRequest := &pb.HttpRequest{\n\t\tId:     relayRequest.Id,\n\t\tMethod: proto.String(\"GET\"),\n\t\tHost:   proto.String(\"example.com\"),\n\t\tUrl:    proto.String(\"http://invalid/bar?a=b#c\"),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-Deadline\"),\n\t\t\tValue: proto.String(\"now\"),\n\t\t}},\n\t\tBody: []byte(\"body\"),\n\t}\n\t// Remove the Traceparent header entry since we cannot assert on its value.\n\ttempHeader := relayRequest.Header[:0]\n\tfor _, header := range relayRequest.Header {\n\t\tif *header.Name != \"Traceparent\" {\n\t\t\ttempHeader = append(tempHeader, header)\n\t\t}\n\t}\n\trelayRequest.Header = tempHeader\n\tif !proto.Equal(wantRequest, relayRequest) {\n\t\tt.Errorf(\"Wrong encapsulated request; want %s; got '%s'\", wantRequest, relayRequest)\n\t}\n\n\tserver.b.SendResponse(&pb.HttpResponse{\n\t\tId:         relayRequest.Id,\n\t\tStatusCode: proto.Int32(201),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\"),\n\t\t}},\n\t\tBody: []byte(\"thebody\"),\n\t\tTrailer: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"Some-Trailer\"),\n\t\t\tValue: proto.String(\"trailer value\"),\n\t\t}},\n\t\tEof: proto.Bool(true),\n\t})\n\n\twg.Wait()\n\tresp := respRecorder.Result()\n\tcheckResponse(t, resp, 201, \"thebody\")\n\tif want, got := 1, len(resp.Header); want != got {\n\t\tt.Errorf(\"Wrong # of headers; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"google.com\", resp.Header.Get(\"X-GFE\"); want != got {\n\t\tt.Errorf(\"Wrong header value; want %s; got %s\", want, got)\n\t}\n\tif want, got := 1, len(resp.Trailer); want != got {\n\t\tt.Errorf(\"Wrong # of trailers; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"trailer value\", resp.Trailer.Get(\"Some-Trailer\"); want != got {\n\t\tt.Errorf(\"Wrong trailer value; want %s; got %s\", want, got)\n\t}\n}\n\nfunc TestClientHandlerWithChunkedResponse(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/client/foo/bar?a=b#c\", strings.NewReader(\"body\"))\n\treq.Header.Add(\"X-Deadline\", \"now\")\n\trespRecorder := httptest.NewRecorder()\n\tserver := NewServer(Config{})\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() { server.userClientRequest(respRecorder, req); wg.Done() }()\n\trelayRequest, err := server.b.GetRequest(context.Background(), \"foo\", \"/\")\n\tif err != nil {\n\t\tt.Errorf(\"Error when getting request: %v\", err)\n\t}\n\n\twantRequest := &pb.HttpRequest{\n\t\tId:     relayRequest.Id,\n\t\tMethod: proto.String(\"GET\"),\n\t\tHost:   proto.String(\"example.com\"),\n\t\tUrl:    proto.String(\"http://invalid/bar?a=b#c\"),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-Deadline\"),\n\t\t\tValue: proto.String(\"now\"),\n\t\t}},\n\t\tBody: []byte(\"body\"),\n\t}\n\t// Remove the Traceparent header entry since we cannot assert on its value.\n\ttempHeader := relayRequest.Header[:0]\n\tfor _, header := range relayRequest.Header {\n\t\tif *header.Name != \"Traceparent\" {\n\t\t\ttempHeader = append(tempHeader, header)\n\t\t}\n\t}\n\trelayRequest.Header = tempHeader\n\tif !proto.Equal(wantRequest, relayRequest) {\n\t\tt.Errorf(\"Wrong encapsulated request; want %s; got '%s'\", wantRequest, relayRequest)\n\t}\n\n\tserver.b.SendResponse(&pb.HttpResponse{\n\t\tId:         relayRequest.Id,\n\t\tStatusCode: proto.Int32(201),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\"),\n\t\t}},\n\t\tBody: []byte(\"the\"),\n\t})\n\n\tserver.b.SendResponse(&pb.HttpResponse{\n\t\tId:   relayRequest.Id,\n\t\tBody: []byte(\"body\"),\n\t\tTrailer: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"Some-Trailer\"),\n\t\t\tValue: proto.String(\"trailer value\"),\n\t\t}},\n\t\tEof: proto.Bool(true),\n\t})\n\n\twg.Wait()\n\tresp := respRecorder.Result()\n\tcheckResponse(t, resp, 201, \"thebody\")\n\tif want, got := 1, len(resp.Header); want != got {\n\t\tt.Errorf(\"Wrong # of headers; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"google.com\", resp.Header.Get(\"X-GFE\"); want != got {\n\t\tt.Errorf(\"Wrong header value; want %s; got %s\", want, got)\n\t}\n\tif want, got := 1, len(resp.Trailer); want != got {\n\t\tt.Errorf(\"Wrong # of trailers; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"trailer value\", resp.Trailer.Get(\"Some-Trailer\"); want != got {\n\t\tt.Errorf(\"Wrong trailer value; want %s; got %s\", want, got)\n\t}\n}\n\nfunc TestClientBadRequest(t *testing.T) {\n\ttests := []struct {\n\t\tdesc     string\n\t\treq      *http.Request\n\t\twantCode int\n\t\twantMsg  string\n\t}{\n\t\t{\n\t\t\tdesc:     \"url-path misses the backend name and path\",\n\t\t\treq:      httptest.NewRequest(\"GET\", \"/client/\", strings.NewReader(\"body\")),\n\t\t\twantCode: 400,\n\t\t\twantMsg:  \"Request path too short:\",\n\t\t},\n\t\t{\n\t\t\tdesc:     \"url-path misses the backend header\",\n\t\t\treq:      httptest.NewRequest(\"GET\", \"/\", strings.NewReader(\"body\")),\n\t\t\twantCode: 400,\n\t\t\twantMsg:  \"Request without required header:\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\trespRecorder := httptest.NewRecorder()\n\t\t\tserver := NewServer(Config{})\n\t\t\twg := sync.WaitGroup{}\n\t\t\twg.Add(1)\n\t\t\tgo func() { server.userClientRequest(respRecorder, tc.req); wg.Done() }()\n\t\t\twg.Wait()\n\n\t\t\tresp := respRecorder.Result()\n\t\t\tif got := resp.StatusCode; tc.wantCode != got {\n\t\t\t\tt.Errorf(\"Wrong response code; want %d; got %d\", tc.wantCode, got)\n\t\t\t}\n\t\t\tif tc.wantMsg != \"\" {\n\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Failed to read body stream: %v\", err)\n\t\t\t\t}\n\t\t\t\tif !strings.Contains(string(body), tc.wantMsg) {\n\t\t\t\t\tt.Errorf(\"Wrong response body; want %q; got %q\", tc.wantMsg, body)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc nonRepeatingByteArray(n int) []byte {\n\tresult := make([]byte, 0, n)\n\ti := 0\n\tfor len(result) < n {\n\t\tresult = append(result, []byte(fmt.Sprintf(\"%8d\", i))...)\n\t\ti += 1\n\t}\n\treturn result\n}\n\nfunc TestRequestStreamHandler(t *testing.T) {\n\tblockSize := 64\n\twantRequestStream := nonRepeatingByteArray(3 * blockSize)\n\n\t// In a background goroutine, run a client request with post-request data\n\t// in the request stream.\n\treq := httptest.NewRequest(\"GET\", \"/client/foo/bar?a=b#c\", strings.NewReader(\"body\"))\n\treq.Header.Add(\"X-Deadline\", \"now\")\n\trespRecorder := hijacktest.NewRecorder(wantRequestStream)\n\tserver := NewServer(Config{BlockSize: blockSize})\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() { server.userClientRequest(respRecorder, req); wg.Done() }()\n\n\t// Simulate a 101 Switching Protocols response from the backend.\n\trelayRequest, err := server.b.GetRequest(context.Background(), \"foo\", \"/\")\n\tif err != nil {\n\t\tt.Errorf(\"Error when getting request: %v\", err)\n\t}\n\tserver.b.SendResponse(&pb.HttpResponse{\n\t\tId:         relayRequest.Id,\n\t\tStatusCode: proto.Int32(101),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"Upgrade\"),\n\t\t\tValue: proto.String(\"SPDY/3.1\"),\n\t\t}},\n\t\tBody: []byte(\"the\"),\n\t})\n\n\t// Get the data from the request stream and check its contents.\n\tgotRequestStream := []byte{}\n\tfor len(gotRequestStream) < len(wantRequestStream) {\n\t\treqstreamRecorder := httptest.NewRecorder()\n\t\tstreamreq := httptest.NewRequest(\"POST\", \"/server/requeststream?id=\"+*relayRequest.Id, nil)\n\t\tserver.serverRequestStream(reqstreamRecorder, streamreq)\n\t\tswitch sc := reqstreamRecorder.Result().StatusCode; sc {\n\t\tcase http.StatusOK:\n\t\t\tgotRequestStream = append(gotRequestStream, reqstreamRecorder.Body.Bytes()...)\n\t\tcase http.StatusGone:\n\t\t\tbreak\n\t\tdefault:\n\t\t\tt.Errorf(\"POST /server/requeststream returned unexpected status %d, want %d or %d\", sc, http.StatusOK, http.StatusGone)\n\t\t}\n\t}\n\tif !bytes.Equal(wantRequestStream, gotRequestStream) {\n\t\tt.Errorf(\"POST /server/requeststream returned unexpected data, got:\\n%s\\nwant:\\n%s\", gotRequestStream, wantRequestStream)\n\t}\n\n\t// Terminate the client request and verify the response.\n\tserver.b.SendResponse(&pb.HttpResponse{\n\t\tId:   relayRequest.Id,\n\t\tBody: []byte(\"body\"),\n\t\tEof:  proto.Bool(true),\n\t})\n\twg.Wait()\n\tcheckResponse(t, respRecorder.Result(), 101, \"thebody\")\n}\n\nfunc TestServerRequestResponseHandler(t *testing.T) {\n\tbackendReq := &pb.HttpRequest{\n\t\tId:     proto.String(\"15\"),\n\t\tMethod: proto.String(\"GET\"),\n\t\tUrl:    proto.String(\"http://invalid/my/url\"),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\"),\n\t\t}},\n\t\tBody: []byte(\"thebody\"),\n\t}\n\n\tbackendResp := &pb.HttpResponse{\n\t\tId:         backendReq.Id,\n\t\tStatusCode: proto.Int32(201),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\"),\n\t\t}},\n\t\tBody: []byte(\"thebody\"),\n\t\tEof:  proto.Bool(true),\n\t}\n\tbackendRespBody, err := proto.Marshal(backendResp)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to marshal test response: %v\", err)\n\t}\n\n\treq := httptest.NewRequest(\"GET\", \"/server/request?server=b\", strings.NewReader(\"\"))\n\tresp := httptest.NewRequest(\"POST\", \"/server/response\", bytes.NewReader(backendRespBody))\n\treqRecorder := httptest.NewRecorder()\n\trespRecorder := httptest.NewRecorder()\n\tserver := NewServer(Config{})\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tserver.serverRequest(reqRecorder, req)\n\t\tserver.serverResponse(respRecorder, resp)\n\t\twg.Done()\n\t}()\n\n\t// create the request channel to avoid 503 error for unknown clients.\n\tserver.b.req[\"b\"] = make(chan *pb.HttpRequest)\n\tserverRespChan, err := server.b.RelayRequest(\"b\", backendReq)\n\tif err != nil {\n\t\tt.Errorf(\"Got relay request error: %v\", err)\n\t}\n\tserverResp := <-serverRespChan\n\twg.Wait()\n\n\tif want, got := 200, reqRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tbody, err := io.ReadAll(reqRecorder.Result().Body)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to read body stream: %v\", err)\n\t}\n\tif !strings.Contains(string(body), \"/my/url\") {\n\t\tt.Errorf(\"Serialize request didn't contain URL: %s\", string(body))\n\t}\n\tif !strings.Contains(string(body), \"X-GFE\") {\n\t\tt.Errorf(\"Serialize request didn't contain header: %s\", string(body))\n\t}\n\n\tif want, got := 200, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\n\tif !proto.Equal(serverResp, backendResp) {\n\t\tt.Errorf(\"Encapsulated response was garbled; want %s; got %s\", backendResp, serverResp)\n\t}\n}\n\nfunc TestServerResponseHandlerWithInvalidRequestID(t *testing.T) {\n\tbackendResp := &pb.HttpResponse{\n\t\tId:         proto.String(\"not found\"),\n\t\tStatusCode: proto.Int32(201),\n\t\tHeader: []*pb.HttpHeader{{\n\t\t\tName:  proto.String(\"X-GFE\"),\n\t\t\tValue: proto.String(\"google.com\"),\n\t\t}},\n\t\tBody: []byte(\"thebody\"),\n\t\tEof:  proto.Bool(true),\n\t}\n\tbackendRespBody, err := proto.Marshal(backendResp)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to marshal test response: %v\", err)\n\t}\n\n\tresp := httptest.NewRequest(\"POST\", \"/server/response\", bytes.NewReader(backendRespBody))\n\trespRecorder := httptest.NewRecorder()\n\tserver := NewServer(Config{})\n\tserver.serverResponse(respRecorder, resp)\n\n\tif want, got := http.StatusBadRequest, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"serverResponse() gave wrong status code; want %d; got %d\", want, got)\n\t}\n}\n\n// Test that a user client request to a backend that has not been seen before\n// immediately returns 503 Service Unavailable.\nfunc TestRequestToUnknownBackendResponse503(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/client/test/path\", bytes.NewReader([]byte{}))\n\trespRecorder := httptest.NewRecorder()\n\tserver := NewServer(Config{})\n\tserver.userClientRequest(respRecorder, req)\n\tif respRecorder.Code != http.StatusServiceUnavailable {\n\t\tt.Errorf(\"Expected status 503, got %d\", respRecorder.Code)\n\t}\n\tbody, err := io.ReadAll(respRecorder.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpected := []byte(\"Cannot reach the client \\\"test\\\"\")\n\tif !bytes.HasPrefix(body, expected) {\n\t\tt.Errorf(\"Unexpected body prefix\\nWant: %s\\nGot: %s\", expected, body)\n\t}\n\tif respRecorder.Header().Get(\"X-CLOUDROBOTICS-HTTP-RELAY\") == \"\" {\n\t\tt.Error(\"Missing X-CLOUDROBOTICS-HTTP-RELAY header\")\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/hw-exporter/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\", \"go_test\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/hw-exporter\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_jaypipes_ghw//:go_default_library\",\n        \"@com_github_jaypipes_ghw//pkg/option:go_default_library\",\n        \"@com_github_jaypipes_ghw//pkg/util:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"hw-exporter\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n\npkg_tar(\n    name = \"hw-exporter-image-layer\",\n    srcs = [\":hw-exporter\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"hw-exporter-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/hw-exporter\"],\n    tars = [\":hw-exporter-image-layer\"],\n    visibility = [\"//visibility:public\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"main_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"@com_github_jaypipes_ghw//:go_default_library\",\n        \"@com_github_jaypipes_ghw//pkg/option:go_default_library\",\n        \"@com_github_jaypipes_ghw//pkg/pci:go_default_library\",\n        \"@com_github_jaypipes_ghw//pkg/util:go_default_library\",\n        \"@com_github_jaypipes_pcidb//:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus/testutil:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/hw-exporter/main.go",
    "content": "// hw-exporter exposes a Prometheus metric pci_device_count that indicates the\n// number of each PCI device type (vendor/product/class/driver) installed on\n// this node.\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/jaypipes/ghw\"\n\t\"github.com/jaypipes/ghw/pkg/option\"\n\t\"github.com/jaypipes/ghw/pkg/util\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\nvar (\n\tmetricsPort = flag.Int(\"metrics-port\", 9999, \"Port to expose Prometheus metrics on.\")\n\tlogLevel    = flag.Int(\"log-level\", int(slog.LevelInfo), \"the log message level required to be logged\")\n\tchroot      = flag.String(\"chroot\", \"/\", \"Path to chroot into before collecting hardware info.\")\n)\n\ntype pciCollector struct {\n\tpciDeviceCount *prometheus.Desc\n}\n\nfunc newPciCollector() *pciCollector {\n\treturn &pciCollector{\n\t\tpciDeviceCount: prometheus.NewDesc(\n\t\t\t\"pci_device_count\",\n\t\t\t\"Number of PCI devices by vendor, product, class, and driver.\",\n\t\t\t[]string{\"vendor\", \"product\", \"class\", \"driver\"},\n\t\t\tnil,\n\t\t),\n\t}\n}\n\n// Describe implements the prometheus.Collector interface.\nfunc (c *pciCollector) Describe(ch chan<- *prometheus.Desc) {\n\tch <- c.pciDeviceCount\n}\n\n// getNameOrID returns the name of a PCI device component, or its ID if the name is unknown.\nfunc getNameOrID(name, id string) string {\n\tif name == util.UNKNOWN {\n\t\treturn \"0x\" + id\n\t}\n\treturn name\n}\n\n// Collect implements the prometheus.Collector interface, counting the number of\n// devices by vendor/product/class/driver.\nfunc (c *pciCollector) Collect(ch chan<- prometheus.Metric) {\n\tpciInfo, err := ghw.PCI(&option.Option{Chroot: chroot})\n\tif err != nil {\n\t\tslog.Error(\"Failed to get PCI info\", ilog.Err(err))\n\t\treturn\n\t}\n\n\tdeviceCounts := make(map[[4]string]int)\n\tfor _, device := range pciInfo.Devices {\n\t\tvendor := getNameOrID(device.Vendor.Name, device.Vendor.ID)\n\t\tproduct := getNameOrID(device.Product.Name, device.Product.ID)\n\t\tclass := getNameOrID(device.Class.Name, device.Class.ID)\n\t\tlabels := [4]string{vendor, product, class, device.Driver}\n\t\tdeviceCounts[labels]++\n\t}\n\n\tfor labels, count := range deviceCounts {\n\t\tch <- prometheus.MustNewConstMetric(c.pciDeviceCount, prometheus.GaugeValue, float64(count), labels[0], labels[1], labels[2], labels[3])\n\t}\n}\n\nfunc main() {\n\tflag.Parse()\n\tlogHandler := ilog.NewLogHandler(slog.Level(*logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\t// Run once on startup to test container setup, this is useful during development.\n\t_, err := ghw.PCI(&option.Option{Chroot: chroot})\n\tif err != nil {\n\t\tslog.Error(\"Failed to get PCI info\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\t// Construct and run the metrics server until stopped by k8s (or Ctrl+C).\n\tctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)\n\tdefer cancel()\n\n\tregistry := prometheus.NewRegistry()\n\tregistry.MustRegister(newPciCollector())\n\n\tmux := http.NewServeMux()\n\tmux.Handle(\"/metrics\", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))\n\n\tserver := &http.Server{\n\t\tAddr:    fmt.Sprintf(\":%d\", *metricsPort),\n\t\tHandler: mux,\n\t}\n\n\tgo func() {\n\t\tslog.Info(\"Starting metrics server\", slog.Int(\"port\", *metricsPort))\n\t\tif err := server.ListenAndServe(); err != http.ErrServerClosed {\n\t\t\tslog.Error(\"Metrics server failed\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}()\n\n\t// Call Shutdown() in the main goroutine because ListenAndServe() returns\n\t// immediately but if the main goroutine ends then, the process will stop\n\t// before finishing any ongoing requests.\n\t<-ctx.Done()\n\tslog.Info(\"Shutting down metrics server...\")\n\tserver.Shutdown(context.Background())\n}\n"
  },
  {
    "path": "src/go/cmd/hw-exporter/main_test.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/jaypipes/ghw\"\n\t\"github.com/jaypipes/ghw/pkg/option\"\n\t\"github.com/jaypipes/ghw/pkg/pci\"\n\t\"github.com/jaypipes/ghw/pkg/util\"\n\t\"github.com/jaypipes/pcidb\"\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n)\n\nvar (\n\ttestVendor = pcidb.Vendor{\n\t\tName: \"Intel Corporation\",\n\t\tID:   \"8086\",\n\t}\n\tunknownVendor = pcidb.Vendor{\n\t\tName: util.UNKNOWN,\n\t\tID:   \"1234\",\n\t}\n\ttestProduct = pcidb.Product{\n\t\tName: \"Ethernet Connection (17) I219-LM\",\n\t\tID:   \"1a1c\",\n\t}\n\tunknownProduct = pcidb.Product{\n\t\tName: util.UNKNOWN,\n\t\tID:   \"5678\",\n\t}\n\ttestClass = pcidb.Class{\n\t\tName: \"Ethernet controller\",\n\t\tID:   \"0200\",\n\t}\n\tunknownClass = pcidb.Class{\n\t\tName: util.UNKNOWN,\n\t\tID:   \"9abc\",\n\t}\n)\n\nfunc TestPciCollector_Collect(t *testing.T) {\n\toldPCI := ghw.PCI\n\tt.Cleanup(func() {\n\t\tghw.PCI = oldPCI\n\t})\n\n\ttests := []struct {\n\t\tdesc    string\n\t\tdevices []*pci.Device\n\t\twant    string\n\t}{{\n\t\tdesc: \"known devices\",\n\t\tdevices: []*pci.Device{{\n\t\t\tVendor:  &testVendor,\n\t\t\tProduct: &testProduct,\n\t\t\tClass:   &testClass,\n\t\t\tDriver:  \"testdriver1\",\n\t\t}, {\n\t\t\tVendor:  &testVendor,\n\t\t\tProduct: &testProduct,\n\t\t\tClass:   &testClass,\n\t\t\tDriver:  \"testdriver2\",\n\t\t}, {\n\t\t\tVendor:  &testVendor,\n\t\t\tProduct: &testProduct,\n\t\t\tClass:   &testClass,\n\t\t\tDriver:  \"testdriver2\",\n\t\t}},\n\t\twant: `\n\t\t\t# HELP pci_device_count Number of PCI devices by vendor, product, class, and driver.\n\t\t\t# TYPE pci_device_count gauge\n\t\t\tpci_device_count{class=\"Ethernet controller\",driver=\"testdriver1\",product=\"Ethernet Connection (17) I219-LM\",vendor=\"Intel Corporation\"} 1\n\t\t\tpci_device_count{class=\"Ethernet controller\",driver=\"testdriver2\",product=\"Ethernet Connection (17) I219-LM\",vendor=\"Intel Corporation\"} 2\n\t\t`,\n\t}, {\n\t\tdesc: \"unknown device\",\n\t\tdevices: []*pci.Device{{\n\t\t\tVendor:  &unknownVendor,\n\t\t\tProduct: &unknownProduct,\n\t\t\tClass:   &unknownClass,\n\t\t\tDriver:  \"testdriver\",\n\t\t}},\n\t\twant: `\n\t\t\t# HELP pci_device_count Number of PCI devices by vendor, product, class, and driver.\n\t\t\t# TYPE pci_device_count gauge\n\t\t\tpci_device_count{class=\"0x9abc\",driver=\"testdriver\",product=\"0x5678\",vendor=\"0x1234\"} 1\n\t\t`,\n\t}}\n\n\tcollector := newPciCollector()\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tghw.PCI = func(opts ...*option.Option) (*pci.Info, error) {\n\t\t\t\treturn &pci.Info{Devices: tc.devices}, nil\n\t\t\t}\n\t\t\tif err := testutil.CollectAndCompare(collector, strings.NewReader(tc.want), \"pci_device_count\"); err != nil {\n\t\t\t\tt.Errorf(\"unexpected collecting result:\\n%s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\", \"go_test\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"coredns.go\",\n        \"main.go\",\n        \"metadata.go\",\n        \"nftables.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/metadata-server\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_fsnotify_fsnotify//:go_default_library\",\n        \"@com_github_google_nftables//:go_default_library\",\n        \"@com_github_google_nftables//expr:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@org_golang_google_api//cloudresourcemanager/v1:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\n        \"coredns_test.go\",\n        \"main_test.go\",\n        \"metadata_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/fake:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"metadata-server-app\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n)\n\npkg_tar(\n    name = \"metadata-server-image-layer\",\n    srcs = [\":metadata-server-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"metadata-server-image\",\n    base = \"@iptables_base\",\n    entrypoint = [\"/metadata-server-app\"],\n    tars = [\":metadata-server-image-layer\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/metadata-server/coredns.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nconst (\n\tconfigMapName      = \"coredns\"\n\tconfigMapNamespace = \"kube-system\"\n\tcorefileName       = \"Corefile\"\n\tzoneStart          = \".:53 {\\n\"\n\tzoneStartPatched   = `.:53 {\n    hosts hosts metadata.google.internal {\n        169.254.169.254 metadata.google.internal\n        fallthrough\n    }\n`\n\thostsStart        = \"hosts {\\n\"\n\thostsStartPatched = `hosts hosts metadata.google.internal host.minikube.internal {\n        169.254.169.254 metadata.google.internal\n`\n)\n\nvar (\n\thostsHostMinikubeInternalPattern = regexp.MustCompile(hostsStart + `(\\s*[.\\d]+ host\\.minikube\\.internal\\n\\s*fallthrough\\n\\s*\\})`)\n\thostsAnyPattern                  = regexp.MustCompile(`hosts [^{]*{\\n`)\n)\n\nfunc getCorefile(ctx context.Context, k8s kubernetes.Interface) (*v1.ConfigMap, error) {\n\tcm, err := k8s.CoreV1().ConfigMaps(configMapNamespace).Get(ctx, configMapName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get ConfigMap %s: %v\", configMapName, err)\n\t}\n\tif _, ok := cm.Data[corefileName]; !ok {\n\t\treturn nil, fmt.Errorf(\"ConfigMap %s doesn't contain key %s\", configMapName, corefileName)\n\t}\n\tif !strings.Contains(cm.Data[corefileName], zoneStart) {\n\t\treturn nil, fmt.Errorf(\"zone start %q not found in Corefile\", zoneStart)\n\t}\n\treturn cm, nil\n}\n\nfunc writeCorefile(ctx context.Context, k8s kubernetes.Interface, cm *v1.ConfigMap) error {\n\t_, err := k8s.CoreV1().ConfigMaps(configMapNamespace).Update(ctx, cm, metav1.UpdateOptions{})\n\treturn err\n}\n\n// PatchCorefile reads the CoreDNS config map and patches the Corefile to resolve\n// metadata.google.internal to 169.254.169.254.\nfunc PatchCorefile(ctx context.Context, k8s kubernetes.Interface) error {\n\tcm, err := getCorefile(ctx, k8s)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// must be idempotent\n\tif strings.Contains(cm.Data[corefileName], zoneStartPatched) || strings.Contains(cm.Data[corefileName], hostsStartPatched) {\n\t\treturn nil\n\t}\n\tif m := hostsHostMinikubeInternalPattern.FindStringIndex(cm.Data[corefileName]); m != nil {\n\t\tcm.Data[corefileName] = hostsHostMinikubeInternalPattern.ReplaceAllString(cm.Data[corefileName], hostsStartPatched+\"$1\")\n\t} else {\n\t\tcm.Data[corefileName] = strings.Replace(cm.Data[corefileName], zoneStart, zoneStartPatched, 1)\n\t}\n\tif len(hostsAnyPattern.FindAllString(cm.Data[corefileName], -1)) > 1 {\n\t\treturn fmt.Errorf(\"multiple hosts entries after patching, please check the input\")\n\t}\n\n\treturn writeCorefile(ctx, k8s, cm)\n}\n\n// RevertCorefile undoes the effect of PatchCorefile.\nfunc RevertCorefile(ctx context.Context, k8s kubernetes.Interface) error {\n\tcm, err := getCorefile(ctx, k8s)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainsZoneStartPatch := strings.Contains(cm.Data[corefileName], zoneStartPatched)\n\tcontainsHostsStartPatch := strings.Contains(cm.Data[corefileName], hostsStartPatched)\n\t// must be idempotent\n\tif !containsZoneStartPatch && !containsHostsStartPatch {\n\t\treturn nil\n\t}\n\tif containsZoneStartPatch && !containsHostsStartPatch {\n\t\tcm.Data[corefileName] = strings.Replace(cm.Data[corefileName], zoneStartPatched, zoneStart, 1)\n\t} else if !containsZoneStartPatch && containsHostsStartPatch {\n\t\tcm.Data[corefileName] = strings.Replace(cm.Data[corefileName], hostsStartPatched, hostsStart, 1)\n\t} else {\n\t\treturn fmt.Errorf(\"cannot contain both patches\")\n\t}\n\treturn writeCorefile(ctx, k8s, cm)\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/coredns_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nconst (\n\tdefaultCorefileBeforeMinikube121 = `.:53 {\n    whoami\n}\n`\n\tmodifiedCorefileBeforeMinikube121 = `.:53 {\n    hosts hosts metadata.google.internal {\n        169.254.169.254 metadata.google.internal\n        fallthrough\n    }\n    whoami\n}\n`\n\tdefaultCorefileAfterMinikube121 = `.:53 {\n    whoami\n    hosts {\n       127.0.0.1 host.minikube.internal\n       fallthrough\n    }\n}\n`\n\tmodifiedCorefileAfterMinikube121 = `.:53 {\n    whoami\n    hosts hosts metadata.google.internal host.minikube.internal {\n        169.254.169.254 metadata.google.internal\n       127.0.0.1 host.minikube.internal\n       fallthrough\n    }\n}\n`\n\tdefaultCorefileUnexpected = `.:53 {\n    whoami\n    hosts {\n        127.0.0.1 host2.minikube.internal\n        fallthrough\n    }\n}\n`\n\tdifferentIp = \"1.2.3.456\"\n)\n\nfunc createCorefile(t *testing.T, k8s kubernetes.Interface, corefileData string) {\n\tif _, err := k8s.CoreV1().ConfigMaps(configMapNamespace).Create(\n\t\tcontext.Background(),\n\t\t&v1.ConfigMap{\n\t\t\tData: map[string]string{\n\t\t\t\tcorefileName: corefileData,\n\t\t\t},\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: configMapName,\n\t\t\t},\n\t\t},\n\t\tmetav1.CreateOptions{}); err != nil {\n\t\tt.Errorf(\"error creating ConfigMap %s: %v\", configMapName, err)\n\t}\n}\n\nfunc readCorefile(t *testing.T, k8s kubernetes.Interface) string {\n\tcm, err := k8s.CoreV1().ConfigMaps(configMapNamespace).Get(context.Background(), configMapName, metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Errorf(\"error reading ConfigMap coredns: %v\", err)\n\t\treturn \"\"\n\t}\n\tdata, ok := cm.Data[corefileName]\n\tif !ok {\n\t\tt.Errorf(\"ConfigMap %s doesn't contain key %s\", configMapName, corefileName)\n\t\treturn \"\"\n\t}\n\treturn data\n}\n\nfunc TestPatchCorefile(t *testing.T) {\n\ttests := []struct {\n\t\tdesc  string\n\t\tinput string\n\t\twant  string\n\t}{\n\t\t{\n\t\t\t\"default corefile without host.minikube.internal entry\",\n\t\t\tdefaultCorefileBeforeMinikube121,\n\t\t\tmodifiedCorefileBeforeMinikube121,\n\t\t},\n\t\t{\n\t\t\t\"default corefile with host.minikube.internal entry\",\n\t\t\tdefaultCorefileAfterMinikube121,\n\t\t\tmodifiedCorefileAfterMinikube121,\n\t\t},\n\t\t{\n\t\t\t\"default corefile with host.minikube.internal entry, different IP\",\n\t\t\tstrings.Replace(defaultCorefileAfterMinikube121, \"127.0.0.1\", differentIp, 1),\n\t\t\tstrings.Replace(modifiedCorefileAfterMinikube121, \"127.0.0.1\", differentIp, 1),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tk8s := fake.NewSimpleClientset()\n\t\t\tcreateCorefile(t, k8s, tc.input)\n\n\t\t\tif err := PatchCorefile(ctx, k8s); err != nil {\n\t\t\t\tt.Errorf(\"error in PatchCorefile: %v\", err)\n\t\t\t}\n\t\t\tif got := readCorefile(t, k8s); got != tc.want {\n\t\t\t\tt.Errorf(`want readCorefile(t, k8s) = %q, got %q`, tc.want, got)\n\t\t\t}\n\n\t\t\t// Check that a second patch has no effect.\n\t\t\tif err := PatchCorefile(ctx, k8s); err != nil {\n\t\t\t\tt.Errorf(\"error in second PatchCorefile: %v\", err)\n\t\t\t}\n\t\t\tif got := readCorefile(t, k8s); got != tc.want {\n\t\t\t\tt.Errorf(`after second patch, want readCorefile(t, k8s) = %q, got %q`, tc.want, got)\n\t\t\t}\n\n\t\t\t// Check that reverting undoes the change.\n\t\t\tif err := RevertCorefile(ctx, k8s); err != nil {\n\t\t\t\tt.Errorf(\"error in RevertCorefile: %v\", err)\n\t\t\t}\n\t\t\tif got := readCorefile(t, k8s); got != tc.input {\n\t\t\t\tt.Errorf(`after revert, want readCorefile(t, k8s) = %q, got %q`, tc.input, got)\n\t\t\t}\n\n\t\t\t// Check that a second revert has no effect.\n\t\t\tif err := RevertCorefile(ctx, k8s); err != nil {\n\t\t\t\tt.Errorf(\"error in second RevertCorefile: %v\", err)\n\t\t\t}\n\t\t\tif got := readCorefile(t, k8s); got != tc.input {\n\t\t\t\tt.Errorf(`after second revert, want readCorefile(t, k8s) = %q, got %q`, tc.input, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPatchCorefileUnexpected(t *testing.T) {\n\tctx := context.Background()\n\tk8s := fake.NewSimpleClientset()\n\tcreateCorefile(t, k8s, defaultCorefileUnexpected)\n\n\tif err := PatchCorefile(ctx, k8s); err == nil {\n\t\tt.Error(\"PatchCorefile() succeeded with invalid input, wanted error\")\n\t}\n\tif got := readCorefile(t, k8s); got != defaultCorefileUnexpected {\n\t\tt.Errorf(`unexpected input should not be modified, want readCorefile(t, k8s) = %q, got %q`, defaultCorefileUnexpected, got)\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package main runs a local http server providing details about the connected\n// cloud project.\n//\n// This metadata server replicates a subset of the GKE metadata server\n// functionality to provide application default credentials for local services.\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/googlecloudrobotics/ilog\"\n)\n\nvar (\n\tbindIP      = flag.String(\"bind_ip\", \"127.0.0.1\", \"IPv4 address to listen on\")\n\tport        = flag.Int(\"port\", 80, \"Port number to listen on\")\n\trobotIdFile = flag.String(\"robot_id_file\", \"\", \"robot-id.json file\")\n\tsourceCidr  = flag.String(\"source_cidr\", \"127.0.0.1/32\", \"CIDR giving allowed source addresses for token retrieval\")\n\t// Mirror gke behavior and return token with at least 5 minutes of remaining time.\n\t// https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#applications\n\tminTokenExpiry = flag.Int(\"min_token_expiry\", 300, \"Minimum time a token needs to be valid for in seconds\")\n\tlogPeerDetails = flag.Bool(\"log_peer_details\", false, \"When enabled details about the peer that requests ADC are logged on the expense of some extra latency\")\n\tlogLevel       = flag.Int(\"log_level\", int(slog.LevelInfo), \"the log message level required to be logged\")\n\trunningOnGKE   = flag.Bool(\"running_on_gke\", false, \"If running on GKE, skip setup steps that are unnecessary and will fail.\")\n\trobotSAName    = flag.String(\"service_account\", \"robot-service\", \"Robot default service account name, default: robot-service\")\n)\n\nfunc detectChangesToFile(filename string) <-chan struct{} {\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\tslog.Error(\"NewWatcher\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\terr = watcher.Add(filename)\n\tif err != nil {\n\t\tslog.Error(\"Watcher.Add\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\tchanges := make(chan struct{})\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase event, ok := <-watcher.Events:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tslog.Info(\"event\", slog.String(\"File\", filename), slog.Any(\"Event\", event))\n\t\t\t\tif event.Op&(fsnotify.Write|fsnotify.Remove) != 0 {\n\t\t\t\t\tchanges <- struct{}{}\n\t\t\t\t}\n\t\t\tcase err, ok := <-watcher.Errors:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tslog.Warn(\"watcher\", slog.Any(\"Error\", err))\n\t\t\t}\n\t\t}\n\t}()\n\treturn changes\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tlogHandler := ilog.NewLogHandler(slog.Level(*logLevel), os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tif ip := net.ParseIP(*bindIP); ip == nil {\n\t\tslog.Error(\"invalid bind_ip flag\")\n\t\tos.Exit(1)\n\t}\n\n\tctx := context.Background()\n\n\tconfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tslog.Error(\"Can't create k8s in-cluster config\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\tk8s, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\tslog.Error(\"Can't create k8s client\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\n\ttokenHandler, err := NewTokenHandler(ctx, k8s, *robotSAName)\n\tif err != nil {\n\t\tslog.Error(\"NewTokenHandler\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\n\tidentityHandler, err := NewIdentityHandler(ctx)\n\tif err != nil {\n\t\tslog.Error(\"NewIdentityHandler\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\n\thttp.Handle(\"/computeMetadata/v1/instance/service-accounts/default/token\", tokenHandler)\n\thttp.Handle(\"/computeMetadata/v1/instance/service-accounts/default/identity\", identityHandler)\n\tserviceAccountHandler := ServiceAccountHandler{}\n\thttp.Handle(\"/computeMetadata/v1/instance/service-accounts/default/\", serviceAccountHandler)\n\thttp.Handle(\"/computeMetadata/v1/instance/service-accounts/\", ConstHandler{[]byte(\"default/\\n\")})\n\tmetadataHandler := tokenHandler.NewMetadataHandler(ctx)\n\thttp.Handle(\"/computeMetadata/v1/\", metadataHandler)\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/\" {\n\t\t\tslog.Info(\"Handling root request\", slog.String(\"URL\", r.URL.Path))\n\t\t\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\t\t\tw.Header().Set(\"Content-Type\", \"application/text\")\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tw.Write([]byte(\"computeMetadata/\\n\"))\n\t\t} else {\n\t\t\tslog.Warn(\"Unhandled request\", slog.String(\"URL\", r.URL.Path), slog.String(\"Origin\", r.RemoteAddr))\n\t\t\thttp.NotFound(w, r)\n\t\t}\n\t})\n\t// Return dummy IP addresses for internal/external IPs. cloudprober\n\t// crashes if these are not present.\n\thttp.Handle(\"/computeMetadata/v1/instance/network-interfaces/0/ip\", ConstHandler{[]byte(\"127.0.0.1/\\n\")})\n\thttp.Handle(\"/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip\", ConstHandler{[]byte(\"127.0.0.1/\\n\")})\n\n\t// Make sure that the bind is successful before adding the iptables rule as a means of\n\t// avoiding a race condition where an old metadata-server instance that has not yet fully\n\t// terminated removes the iptables rule again.\n\tbindAddress := fmt.Sprintf(\"%s:%d\", *bindIP, *port)\n\tln, err := net.Listen(\"tcp\", bindAddress)\n\tif err != nil {\n\t\tslog.Error(\"failed to create listener\", slog.String(\"Address\", bindAddress), slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\tslog.Info(\"Listening\", slog.String(\"Address\", bindAddress))\n\n\tif err := addNATRule(*bindIP, *port); err != nil {\n\t\tslog.Error(\"failed to add iptables rule\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}\n\n\tif !*runningOnGKE {\n\t\tif err := PatchCorefile(ctx, k8s); err != nil {\n\t\t\tremoveNATRule()\n\t\t\tslog.Error(\"PatchCorefile\", slog.Any(\"Error\", err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tgo func() {\n\t\terr = http.Serve(ln, nil)\n\t\tif !*runningOnGKE {\n\t\t\tRevertCorefile(ctx, k8s)\n\t\t}\n\t\tremoveNATRule()\n\t\tslog.Error(\"Serve\", slog.Any(\"Error\", err))\n\t\tos.Exit(1)\n\t}()\n\n\tgo func() {\n\t\t<-detectChangesToFile(*robotIdFile)\n\t\tif !*runningOnGKE {\n\t\t\tRevertCorefile(ctx, k8s)\n\t\t}\n\t\tremoveNATRule()\n\t\tslog.Error(\"File changed but reloading is not implemented. Crashing...\", slog.String(\"ID\", *robotIdFile))\n\t\tos.Exit(1)\n\t}()\n\n\tstop := make(chan os.Signal)\n\tsignal.Notify(stop, syscall.SIGTERM)\n\n\t<-stop\n\tif !*runningOnGKE {\n\t\tRevertCorefile(ctx, k8s)\n\t}\n\tremoveNATRule()\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/main_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\twriteTimeout = 100 * time.Millisecond\n)\n\nfunc TestDetectsDeletionOfFile(t *testing.T) {\n\ttmpfile, err := os.CreateTemp(\"\", \"tmpfile\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := tmpfile.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tchanges := detectChangesToFile(tmpfile.Name())\n\tif err := os.Remove(tmpfile.Name()); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tselect {\n\tcase <-changes:\n\t\tbreak\n\tcase <-time.After(writeTimeout):\n\t\tt.Errorf(\"no change detected after %s\", writeTimeout)\n\t}\n}\n\nfunc TestNoChangeDetectedWhenFileUnchanged(t *testing.T) {\n\ttmpfile, err := os.CreateTemp(\"\", \"tmpfile\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := tmpfile.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(tmpfile.Name())\n\tchanges := detectChangesToFile(tmpfile.Name())\n\tselect {\n\tcase <-changes:\n\t\tt.Errorf(\"unexpected change detected after %s\", writeTimeout)\n\tcase <-time.After(writeTimeout):\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/metadata.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package metadata implements the metadat http handlers.\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"golang.org/x/oauth2\"\n\tcloudresourcemanager \"google.golang.org/api/cloudresourcemanager/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nconst (\n\t// getPodByIPRetries configures how often we retry determining the ip for a pod\n\tgetPodByIPRetries = 10\n\t// getPodByIPWait confgured the time to sleep between the retries\n\tgetPodByIPWait = 500 * time.Millisecond\n)\n\n// rateLimitTokenSource is a TokenSource that applies exponential backoff on\n// errors, returning the previous error if called again too soon. It doesn't\n// rate-limit on success, as it assumes it wraps an oauth2.ReuseTokenSource.\ntype rateLimitTokenSource struct {\n\twrapped oauth2.TokenSource\n\n\tmu    sync.Mutex // guards err and next\n\terr   error\n\tdelay time.Duration\n\tnext  time.Time\n}\n\nfunc newRateLimitTokenSource(ts oauth2.TokenSource) *rateLimitTokenSource {\n\trlts := &rateLimitTokenSource{wrapped: ts}\n\trlts.resetBackoff()\n\treturn rlts\n}\n\nvar timeNow = time.Now\n\n// Token tries to fetch a token, unless an error was recently encountered, in\n// which case the previous error is returned.\nfunc (s *rateLimitTokenSource) Token() (*oauth2.Token, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif timeNow().Before(s.next) {\n\t\treturn nil, s.err\n\t}\n\tt, err := s.wrapped.Token()\n\tif err != nil {\n\t\ts.updateBackoff(err)\n\t\treturn nil, err\n\t}\n\ts.resetBackoff()\n\treturn t, nil\n}\n\nfunc (s *rateLimitTokenSource) resetBackoff() {\n\ts.delay = 100 * time.Millisecond\n\ts.next = time.Time{}\n}\n\nfunc (s *rateLimitTokenSource) updateBackoff(err error) {\n\ts.next = timeNow().Add(s.delay)\n\ts.err = err\n\tif s.delay < 120*time.Second {\n\t\ts.delay *= 2\n\t}\n}\n\n// ConstHandler serves OK responses with static body content.\ntype ConstHandler struct {\n\tBody []byte\n}\n\nfunc (ch ConstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\tw.Header().Set(\"Content-Type\", \"application/text\")\n\tw.WriteHeader(http.StatusOK)\n\tw.Write(ch.Body)\n}\n\ntype jwtSource interface {\n\tCreateJWT(context.Context, time.Duration) (string, error)\n}\n\n// IdentityHandler serves JWT identity tokens for the robot\ntype IdentityHandler struct {\n\tAllowedSources *net.IPNet\n\trobotAuth      jwtSource\n}\n\nfunc NewIdentityHandler(ctx context.Context) (*IdentityHandler, error) {\n\t_, allowedSources, err := net.ParseCIDR(*sourceCidr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid source CIDR %s: %w\", *sourceCidr, err)\n\t}\n\trobotAuth, err := robotauth.LoadFromFile(*robotIdFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read robot id file %s: %w\", *robotIdFile, err)\n\t}\n\n\ti := &IdentityHandler{\n\t\tAllowedSources: allowedSources,\n\t\trobotAuth:      robotAuth,\n\t}\n\treturn i, nil\n}\n\nfunc fromAcceptedIP(w http.ResponseWriter, r *http.Request, allowedSources *net.IPNet) bool {\n\tipPort := strings.Split(r.RemoteAddr, \":\")\n\tif len(ipPort) != 2 {\n\t\tslog.Error(\"Unable to obtain IP from remote address\", slog.String(\"Address\", r.RemoteAddr))\n\t\thttp.Error(w, \"Unable to check authorization\", http.StatusInternalServerError)\n\t\treturn false\n\t}\n\tif ip := net.ParseIP(ipPort[0]); ip == nil || !allowedSources.Contains(ip) {\n\t\tslog.Error(\"Rejected remote IP\", slog.String(\"IP\", ipPort[0]))\n\t\thttp.Error(w, \"Access forbidden\", http.StatusForbidden)\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (h *IdentityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif !fromAcceptedIP(w, r, h.AllowedSources) {\n\t\treturn\n\t}\n\n\tjwt, err := h.robotAuth.CreateJWT(r.Context(), time.Minute*15)\n\tif err != nil {\n\t\tslog.Error(\"Unable to create JWT\", ilog.Err(err))\n\t\thttp.Error(w, \"Unable to create jwt\", http.StatusInternalServerError)\n\t\treturn\n\n\t}\n\n\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tw.WriteHeader(http.StatusOK)\n\tw.Write([]byte(jwt))\n\tslog.Info(\"Served identity token\", slog.String(\"Address\", r.RemoteAddr))\n}\n\n// TokenHandler serves access tokens for the associated GCP service-account.\ntype TokenHandler struct {\n\tAllowedSources *net.IPNet\n\tTokenSource    oauth2.TokenSource\n\tClock          func() time.Time\n\trobotAuth      auth\n\tk8s            *kubernetes.Clientset\n\tsaName         string\n}\n\ntype auth interface {\n\tCreateRobotTokenSource(context.Context, ...string) oauth2.TokenSource\n\tprojectID() string\n\trobotName() string\n}\n\ntype rAuth struct {\n\trobotauth.RobotAuth\n}\n\nfunc (a rAuth) projectID() string {\n\treturn a.ProjectId\n}\n\nfunc (a rAuth) robotName() string {\n\treturn a.RobotName\n}\n\ntype TokenResponse struct {\n\tAccessToken  string `json:\"access_token\"`\n\tExpiresInSec int    `json:\"expires_in\"`\n\tTokenType    string `json:\"token_type\"`\n}\n\nfunc NewTokenHandler(ctx context.Context, k8s *kubernetes.Clientset, saName string) (*TokenHandler, error) {\n\t_, allowedSources, err := net.ParseCIDR(*sourceCidr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid source CIDR %s: %w\", *sourceCidr, err)\n\t}\n\n\tt := &TokenHandler{\n\t\tAllowedSources: allowedSources,\n\t\tClock:          time.Now,\n\t\tk8s:            k8s,\n\t\tsaName:         saName,\n\t}\n\tif err := t.updateRobotAuth(); err != nil {\n\t\treturn nil, err\n\t}\n\tt.updateRobotTokenSource(ctx)\n\treturn t, nil\n}\n\nfunc (th *TokenHandler) updateRobotAuth() error {\n\trobotAuth, err := robotauth.LoadFromFile(*robotIdFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read robot id file %s: %w\", *robotIdFile, err)\n\t}\n\tth.robotAuth = &rAuth{*robotAuth}\n\treturn nil\n}\n\nfunc (th *TokenHandler) updateRobotTokenSource(ctx context.Context) {\n\teffectiveSA := th.saName\n\tif effectiveSA == \"\" {\n\t\teffectiveSA = \"robot-service\"\n\t}\n\tif !strings.Contains(effectiveSA, \"@\") {\n\t\teffectiveSA = fmt.Sprintf(\"%s@%s.iam.gserviceaccount.com\", effectiveSA, th.robotAuth.projectID())\n\t}\n\tth.TokenSource = newRateLimitTokenSource(th.robotAuth.CreateRobotTokenSource(ctx, effectiveSA))\n}\n\nfunc (th *TokenHandler) NewMetadataHandler(ctx context.Context) *MetadataHandler {\n\tidHash := fnv.New64a()\n\tidHash.Write([]byte(th.robotAuth.robotName()))\n\n\tret := &MetadataHandler{\n\t\tClusterName:   th.robotAuth.robotName(),\n\t\tProjectId:     th.robotAuth.projectID(),\n\t\tProjectNumber: 0,\n\t\tRobotName:     th.robotAuth.robotName(),\n\t\tInstanceId:    idHash.Sum64(),\n\t\t// This needs to be an actual Cloud zone so that it can be mapped\n\t\t// to a Monarch/Stackdriver region. TODO(swolter): We should make\n\t\t// this zone configurable to avoid confusing users.\n\t\tZone: \"europe-west1-c\",\n\t}\n\tgo backoff.Retry(\n\t\tfunc() error {\n\t\t\tprojectNumber, err := getProjectNumber(oauth2.NewClient(ctx, th.TokenSource), th.robotAuth.projectID())\n\t\t\tif err != nil {\n\t\t\t\tslog.Info(\"will retry to obtain project number\", slog.String(\"Project\", th.robotAuth.projectID()), slog.Any(\"Error\", err))\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tatomic.StoreInt64(&ret.ProjectNumber, projectNumber)\n\t\t\treturn nil\n\t\t},\n\t\tbackoff.NewConstantBackOff(5*time.Second),\n\t)\n\treturn ret\n}\n\n// Return an access token for the 'robot-service' service account\n// The query might also contain a 'scopes' query param, which we currently don't handle\n// (e.g.: scopes=https://www.googleapis.com/auth/devstorage.full_control,https://www.googleapis.com/auth/cloud-platform HTTP/1.1)\nfunc (th *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif !fromAcceptedIP(w, r, th.AllowedSources) {\n\t\treturn\n\t}\n\n\ttoken, err := th.TokenSource.Token()\n\tif err != nil {\n\t\tslog.Error(\"Token retrieval error\", slog.Any(\"Error\", err))\n\t\thttp.Error(w, fmt.Sprintf(\"Token retrieval failed: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tnow := th.Clock()\n\texpiresInSec := int(token.Expiry.Sub(now).Seconds())\n\t// fluent-bit expects expires_in - 10% > 60 seconds\n\tif expiresInSec < *minTokenExpiry {\n\t\tth.updateRobotTokenSource(r.Context())\n\t\ttoken, err = th.TokenSource.Token()\n\t\tif err != nil {\n\t\t\tslog.Error(\"Token retrieval error\", slog.Any(\"Error\", err))\n\t\t\thttp.Error(w, fmt.Sprintf(\"Token retrieval failed: %v\", err), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\texpiresInSec = int(token.Expiry.Sub(now).Seconds())\n\t}\n\n\ttokenResponse := TokenResponse{\n\t\tAccessToken:  token.AccessToken,\n\t\tExpiresInSec: expiresInSec,\n\t\tTokenType:    token.TokenType,\n\t}\n\tbytes, err := json.Marshal(tokenResponse)\n\tif err != nil {\n\t\tslog.Error(\"Token serialization error\", slog.Any(\"Error\", err))\n\t\thttp.Error(w, fmt.Sprintf(\"Token serialization failed: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Collect some extra data for diagnostics (aka which services rely on ADCs).\n\t// User-Agent: \"Fluent-Bit\", \"gcloud-golang/0.1\"\n\tua := r.Header.Get(\"User-Agent\")\n\tpod := th.getPodNameByIP(r.Context(), strings.Split(r.RemoteAddr, \":\")[0])\n\n\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tw.WriteHeader(http.StatusOK)\n\tw.Write(bytes)\n\tslog.Info(\"Served access token\", slog.String(\"Address\", r.RemoteAddr), slog.String(\"UA\", ua), slog.String(\"Pod\", pod))\n}\n\nfunc (th *TokenHandler) getPodNameByIP(ctx context.Context, ip string) string {\n\tif th.k8s == nil || !*logPeerDetails {\n\t\t// TODO(ensonic): need to add a k8s fake to the tests\n\t\treturn \"\"\n\t}\n\n\t// Meassure the time it takes to obtain the extra information\n\tdefer func(start time.Time) {\n\t\tslog.Info(\"getPodNameByIP()\", slog.Duration(\"Duration\", time.Since(start)))\n\t}(time.Now())\n\n\t// TODO(ensonic): to avoid traversing all ns/pods each time we can\n\t// - cache ip->ns/pod mapping\n\t// - check first if we can still get a pod by these keys and if the IP still matches\n\t// - do the listing otherwise\n\t// TODO(ensonic): consider labeling namespaces that participate in ADCs. This will speedup the lookups\n\t// and allows us to lock this down.\n\n\tpodsToRetry := []corev1.Pod{}\n\n\tnss, err := th.k8s.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tslog.Error(\"Failed to list namespaces\", slog.Any(\"Error\", err))\n\t}\n\tfor _, ns := range nss.Items {\n\t\tnsName := ns.ObjectMeta.Name\n\t\tpods, err := th.k8s.CoreV1().Pods(nsName).List(ctx, metav1.ListOptions{})\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to list pods\", slog.String(\"Namespace\", nsName), slog.Any(\"Error\", err))\n\t\t}\n\t\tfor _, pod := range pods.Items {\n\t\t\tif pod.Status.PodIP == ip {\n\t\t\t\treturn nsName + \"/\" + pod.Name\n\t\t\t}\n\t\t\tif pod.Status.PodIP == \"\" {\n\t\t\t\tslog.Warn(\"Pod has no ip (yet)\", slog.String(\"Pod\", pod.Name), slog.String(\"Message\", pod.Status.Message))\n\t\t\t\tpodsToRetry = append(podsToRetry, pod)\n\t\t\t}\n\t\t}\n\t}\n\t// We don't have the resource version from the pod creation (to be used in the ListOptions above). Hence\n\t// we need to do this retry logic for cases where a pod just started and right away asked for an ADC.\n\tretries := getPodByIPRetries\n\tfor len(podsToRetry) > 0 && retries > 0 {\n\t\ttime.Sleep(getPodByIPWait)\n\t\tslog.Info(\"Retrying pods without ip\", slog.Int(\"Count\", len(podsToRetry)))\n\n\t\tptr := podsToRetry\n\t\tpodsToRetry = []corev1.Pod{}\n\t\tfor _, p := range ptr {\n\t\t\tnsName := p.ObjectMeta.Namespace\n\t\t\tpodName := p.ObjectMeta.Name\n\t\t\tpod, err := th.k8s.CoreV1().Pods(nsName).Get(ctx, podName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"Failed to get pod\", slog.String(\"Pod\", podName), slog.String(\"Namespace\", nsName), slog.Any(\"Error\", err))\n\t\t\t}\n\t\t\tif pod.Status.PodIP == ip {\n\t\t\t\treturn nsName + \"/\" + pod.Name\n\t\t\t}\n\t\t\tif pod.Status.PodIP == \"\" {\n\t\t\t\tslog.Info(\"Pod has no ip (yet)\", slog.String(\"Pod\", pod.Name), slog.String(\"Message\", pod.Status.Message))\n\t\t\t\tpodsToRetry = append(podsToRetry, *pod)\n\t\t\t}\n\t\t}\n\t\tretries--\n\t}\n\tslog.Info(\"No pod found\", slog.String(\"IP\", ip))\n\treturn \"\"\n}\n\n// ServiceAccountHandler serves information about the default service account.\ntype ServiceAccountHandler struct {\n}\n\ntype ServiceAccountResponse struct {\n\tAliases []string `json:\"aliases\"`\n\tEmail   string   `json:\"email\"`\n\tScopes  []string `json:\"scopes\"`\n}\n\nfunc (sh ServiceAccountHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tserviceAccountResponse := ServiceAccountResponse{\n\t\tAliases: []string{},\n\t\tEmail:   \"default\",\n\t\tScopes:  []string{},\n\t}\n\n\tbytes, err := json.Marshal(serviceAccountResponse)\n\tif err != nil {\n\t\tslog.Error(\"ServiceAccountResponse serialization error\", slog.Any(\"Error\", err))\n\t\thttp.Error(w, fmt.Sprintf(\"ServiceAccountResponse serialization failed: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(http.StatusOK)\n\tw.Write(bytes)\n\tslog.Info(\"Responded to service-account request\", slog.String(\"Origin\", r.RemoteAddr), slog.String(\"URL\", r.URL.Path))\n}\n\n// MetadataHandler serves generic instance metadata.\ntype MetadataHandler struct {\n\tClusterName   string\n\tProjectId     string\n\tProjectNumber int64\n\tRobotName     string\n\tInstanceId    uint64\n\tZone          string\n}\n\nfunc (mh MetadataHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\tw.Header().Set(\"Content-Type\", \"application/text\")\n\n\tprojectNumber := atomic.LoadInt64(&mh.ProjectNumber)\n\tif projectNumber == 0 {\n\t\tslog.Warn(\"Metadata endpoint was requested before it was ready\")\n\t\thttp.Error(w, \"Metadata endpoint not ready yet\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tmetadata := map[string]string{\n\t\t\"project/project-id\":                   mh.ProjectId,\n\t\t\"project/numeric-project-id\":           fmt.Sprintf(\"%d\", projectNumber),\n\t\t\"instance/hostname\":                    fmt.Sprintf(\"robot-%s\", mh.RobotName),\n\t\t\"instance/id\":                          fmt.Sprintf(\"%d\", mh.InstanceId),\n\t\t\"instance/zone\":                        fmt.Sprintf(\"projects/%d/zones/%s\", projectNumber, mh.Zone),\n\t\t\"instance/attributes/\":                 \"kube-env\\ncluster-name\\ncluster-location\\n\",\n\t\t\"instance/attributes/kube-env\":         fmt.Sprintf(\"CLUSTER_NAME: %s\\n\", mh.ClusterName),\n\t\t\"instance/attributes/cluster-name\":     mh.ClusterName,\n\t\t\"instance/attributes/cluster-location\": mh.Zone,\n\t}\n\n\tkey := strings.TrimPrefix(r.URL.Path, \"/computeMetadata/v1/\")\n\tvalue := metadata[key]\n\tif value == \"\" {\n\t\tslog.Warn(\"No key found\", slog.String(\"URL\", r.URL.Path))\n\t\thttp.NotFound(w, r)\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n\tw.Write([]byte(value))\n\tslog.Info(\"Responded to metadata request\", slog.String(\"URL\", r.URL.Path), slog.String(\"Value\", value))\n}\n\nfunc getProjectNumber(client *http.Client, projectId string) (int64, error) {\n\tcrm, err := cloudresourcemanager.New(client)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tproject, err := crm.Projects.Get(projectId).Do()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn project.ProjectNumber, nil\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/metadata_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n)\n\nfunc bodyOrDie(r *http.Response) string {\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tslog.Error(\"Failed to read body stream\")\n\t\tos.Exit(1)\n\t}\n\treturn string(body)\n}\n\nfunc TestConstHandler(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/url\", strings.NewReader(\"body\"))\n\trespRecorder := httptest.NewRecorder()\n\tch := ConstHandler{[]byte(\"response\")}\n\tch.ServeHTTP(respRecorder, req)\n\n\tif want, got := 200, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"response\", bodyOrDie(respRecorder.Result()); want != got {\n\t\tt.Errorf(\"Wrong response body; want %s; got %s\", want, got)\n\t}\n}\n\ntype fakeJWTSource struct {\n\tval string\n\td   time.Duration\n}\n\nfunc (s *fakeJWTSource) CreateJWT(_ context.Context, d time.Duration) (string, error) {\n\ts.d = d\n\treturn s.val, nil\n}\n\nfunc TestIdentityHandlerServeHTTP(t *testing.T) {\n\tt.Parallel()\n\th := IdentityHandler{\n\t\tAllowedSources: &net.IPNet{net.IPv4(192, 168, 0, 0), net.CIDRMask(24, 32)},\n\t\trobotAuth:      &fakeJWTSource{val: \"value\"},\n\t}\n\n\tt.Run(\"simple\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/service-accounts/default/identity\", nil)\n\t\treq.RemoteAddr = \"192.168.0.101:8001\"\n\t\trespRecorder := httptest.NewRecorder()\n\t\th.ServeHTTP(respRecorder, req)\n\n\t\tif want, got := http.StatusOK, respRecorder.Result().StatusCode; want != got {\n\t\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t\t}\n\t\tif want, got := \"value\", bodyOrDie(respRecorder.Result()); want != got {\n\t\t\tt.Errorf(\"Wrong response body; want %s; got %s\", want, got)\n\t\t}\n\t})\n\tt.Run(\"outside-addr\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/service-accounts/default/identity\", nil)\n\t\treq.RemoteAddr = \"192.168.1.101:8001\"\n\t\trespRecorder := httptest.NewRecorder()\n\t\th.ServeHTTP(respRecorder, req)\n\n\t\tif want, got := http.StatusForbidden, respRecorder.Result().StatusCode; want != got {\n\t\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t\t}\n\t})\n}\n\nfunc TestTokenHandlerServesToken(t *testing.T) {\n\toldMinTokenExpiry := *minTokenExpiry\n\t*minTokenExpiry = 1\n\tt.Cleanup(func() { *minTokenExpiry = oldMinTokenExpiry })\n\ttestTime := time.Unix(1531319123, 0)\n\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/service-accounts/default/token\", strings.NewReader(\"body\"))\n\treq.RemoteAddr = \"192.168.0.101:8001\"\n\trespRecorder := httptest.NewRecorder()\n\tth := TokenHandler{\n\t\tAllowedSources: &net.IPNet{net.IPv4(192, 168, 0, 0), net.CIDRMask(24, 32)},\n\t\tTokenSource:    oauth2.StaticTokenSource(&oauth2.Token{AccessToken: \"mytoken\", Expiry: testTime.Add(10 * time.Second), TokenType: \"Bearer\"}),\n\t\tClock:          func() time.Time { return testTime },\n\t}\n\tth.ServeHTTP(respRecorder, req)\n\n\tif want, got := 200, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"{\\\"access_token\\\":\\\"mytoken\\\",\\\"expires_in\\\":10,\\\"token_type\\\":\\\"Bearer\\\"}\", bodyOrDie(respRecorder.Result()); want != got {\n\t\tt.Errorf(\"Wrong response body; want %s; got %s\", want, got)\n\t}\n}\n\ntype fakeRobotAuth struct {\n\tts   oauth2.TokenSource\n\tid   string\n\tname string\n}\n\nfunc (a *fakeRobotAuth) CreateRobotTokenSource(context.Context, ...string) oauth2.TokenSource {\n\treturn a.ts\n}\n\nfunc (a *fakeRobotAuth) projectID() string {\n\treturn a.id\n}\n\nfunc (a *fakeRobotAuth) robotName() string {\n\treturn a.name\n}\n\nfunc TestTokenHandlerServesLastingToken(t *testing.T) {\n\toldMinTokenExpiry := *minTokenExpiry\n\t*minTokenExpiry = 300\n\tt.Cleanup(func() { *minTokenExpiry = oldMinTokenExpiry })\n\ttestTime := time.Unix(1531319123, 0)\n\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/service-accounts/default/token\", strings.NewReader(\"body\"))\n\treq.RemoteAddr = \"192.168.0.101:8001\"\n\trespRecorder := httptest.NewRecorder()\n\tth := TokenHandler{\n\t\tAllowedSources: &net.IPNet{net.IPv4(192, 168, 0, 0), net.CIDRMask(24, 32)},\n\t\tTokenSource:    oauth2.StaticTokenSource(&oauth2.Token{AccessToken: \"mytoken\", Expiry: testTime.Add(10 * time.Second), TokenType: \"Bearer\"}),\n\t\tClock:          func() time.Time { return testTime },\n\t\trobotAuth: &fakeRobotAuth{\n\t\t\tts: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: \"mytoken\", Expiry: testTime.Add(1000 * time.Second), TokenType: \"Bearer\"}),\n\t\t},\n\t}\n\tth.ServeHTTP(respRecorder, req)\n\n\tif want, got := 200, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"{\\\"access_token\\\":\\\"mytoken\\\",\\\"expires_in\\\":1000,\\\"token_type\\\":\\\"Bearer\\\"}\", bodyOrDie(respRecorder.Result()); want != got {\n\t\tt.Errorf(\"Wrong response body; want %s; got %s\", want, got)\n\t}\n}\n\nfunc TestTokenHandlerDeniesWrongAddress(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/service-accounts/default/token\", strings.NewReader(\"body\"))\n\treq.RemoteAddr = \"192.168.1.101:8001\"\n\trespRecorder := httptest.NewRecorder()\n\tth := TokenHandler{\n\t\tAllowedSources: &net.IPNet{net.IPv4(192, 168, 0, 0), net.CIDRMask(24, 32)},\n\t\tTokenSource:    oauth2.StaticTokenSource(&oauth2.Token{AccessToken: \"mytoken\"}),\n\t}\n\tth.ServeHTTP(respRecorder, req)\n\n\tif want, got := 403, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n}\n\nfunc TestServiceAccountHandlerReturnsMinimalJSON(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/service-accounts/default/?recursive=true\", strings.NewReader(\"body\"))\n\treq.RemoteAddr = \"192.168.1.101:8001\"\n\trespRecorder := httptest.NewRecorder()\n\tsh := ServiceAccountHandler{}\n\tsh.ServeHTTP(respRecorder, req)\n\n\tif want, got := 200, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"{\\\"aliases\\\":[],\\\"email\\\":\\\"default\\\",\\\"scopes\\\":[]}\", bodyOrDie(respRecorder.Result()); want != got {\n\t\tt.Errorf(\"Wrong response body; want %s; got %s\", want, got)\n\t}\n}\n\nfunc TestMetadataHandlerReturnsZone(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"/computeMetadata/v1/instance/zone\", strings.NewReader(\"body\"))\n\trespRecorder := httptest.NewRecorder()\n\tmh := MetadataHandler{\n\t\tClusterName:   \"28\",\n\t\tProjectId:     \"foo\",\n\t\tProjectNumber: 512,\n\t\tRobotName:     \"28\",\n\t\tZone:          \"edge\",\n\t}\n\tmh.ServeHTTP(respRecorder, req)\n\n\tif want, got := 200, respRecorder.Result().StatusCode; want != got {\n\t\tt.Errorf(\"Wrong response code; want %d; got %d\", want, got)\n\t}\n\tif want, got := \"projects/512/zones/edge\", bodyOrDie(respRecorder.Result()); want != got {\n\t\tt.Errorf(\"Wrong response body; want %s; got %s\", want, got)\n\t}\n}\n\nvar errToken = errors.New(\"failed to get token\")\n\n// fakeTokenSource returns `Errors` consecutive errors then returns tokens.\n// Calls counts the number of calls so far.\ntype fakeTokenSource struct {\n\tCalls  int\n\tErrors int\n}\n\nfunc (s *fakeTokenSource) Token() (*oauth2.Token, error) {\n\ts.Calls = s.Calls + 1\n\tif s.Calls <= s.Errors {\n\t\treturn nil, errToken\n\t}\n\treturn &oauth2.Token{}, nil\n}\n\nfunc TestRateLimitTokenSource(t *testing.T) {\n\toldTimeNow := timeNow\n\tt.Cleanup(func() {\n\t\ttimeNow = oldTimeNow\n\t})\n\n\t// Test that we retry a certain number of errors within a given amount of\n\t// time, then succeed. Since we use 100ms steps (not real time), maxTime\n\t// should not be too large or the test may get slow: One hour can be\n\t// \"simulated\" in a few ms, a year takes ~15s.\n\ttests := []struct {\n\t\tdesc   string\n\t\terrors int\n\t\t// Acceptable range of overall duration (too lazy to do the maths to\n\t\t// work out exactly how long it should wait).\n\t\tminTime time.Duration\n\t\tmaxTime time.Duration\n\t}{\n\t\t{\n\t\t\tdesc:    \"no errors\",\n\t\t\terrors:  0,\n\t\t\tminTime: 0,\n\t\t\tmaxTime: 0,\n\t\t},\n\t\t{\n\t\t\tdesc:    \"single error retried within 0.5s\",\n\t\t\terrors:  1,\n\t\t\tminTime: 100 * time.Millisecond,\n\t\t\tmaxTime: 500 * time.Millisecond,\n\t\t},\n\t\t{\n\t\t\tdesc:    \"two errors retried within 1s\",\n\t\t\terrors:  2,\n\t\t\tminTime: 200 * time.Millisecond,\n\t\t\tmaxTime: time.Second,\n\t\t},\n\t\t{\n\t\t\tdesc: \"20 errors retried within 1h\",\n\n\t\t\terrors:  20,\n\t\t\tminTime: 15 * time.Minute,\n\t\t\tmaxTime: time.Hour,\n\t\t},\n\t}\n\n\tstartTime := time.Time{}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tclock := startTime\n\t\t\ttimeNow = func() time.Time { return clock }\n\n\t\t\tfakeTS := &fakeTokenSource{Errors: tc.errors}\n\t\t\tts := newRateLimitTokenSource(fakeTS)\n\t\t\tfor ; clock.Sub(startTime) <= tc.maxTime; clock = clock.Add(100 * time.Millisecond) {\n\t\t\t\t_, err := ts.Token()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tduration := clock.Sub(startTime)\n\t\t\tif duration < tc.minTime {\n\t\t\t\tt.Errorf(\"Token() succeeded within %s, want at least %s\", duration, tc.minTime)\n\t\t\t}\n\t\t\tif duration > tc.maxTime {\n\t\t\t\tt.Errorf(\"Token() did not succeed within %s\", tc.maxTime)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/metadata-server/nftables.go",
    "content": "// Copyright 2025 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\n\t\"github.com/google/nftables\"\n\t\"github.com/google/nftables/expr\"\n)\n\nvar metadataRule = []byte(\"metadata-nat\")\n\nfunc addNATRule(listenIP string, listenPort int) error {\n\tcon, err := nftables.New()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"nftables new: %v\", err)\n\t}\n\ttable := con.AddTable(&nftables.Table{\n\t\tName:   \"nat\",\n\t\tFamily: nftables.TableFamilyIPv4,\n\t})\n\taccept := nftables.ChainPolicyAccept\n\tprerouting := con.AddChain(&nftables.Chain{\n\t\tName:     \"PREROUTING\",\n\t\tTable:    table,\n\t\tHooknum:  nftables.ChainHookPrerouting,\n\t\tPriority: nftables.ChainPriorityNATDest,\n\t\tType:     nftables.ChainTypeNAT,\n\t\tPolicy:   &accept,\n\t})\n\tdestinationPort := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(destinationPort, uint16(listenPort))\n\tdestinationIP := net.ParseIP(listenIP)\n\tif destinationIP == nil {\n\t\treturn fmt.Errorf(\"%s is not a valid IPv4 address\", listenIP)\n\t}\n\te := []expr.Any{\n\t\t// Load network IP into register 1\n\t\t&expr.Payload{\n\t\t\tOperationType:  expr.PayloadLoad,\n\t\t\tDestRegister:   1,\n\t\t\tSourceRegister: 0,\n\t\t\tBase:           expr.PayloadBaseNetworkHeader,\n\t\t\tOffset:         16,\n\t\t\tLen:            4,\n\t\t\tCsumType:       expr.CsumTypeNone,\n\t\t\t//CsumOffset:     0,\n\t\t\t//CsumFlags:      0,\n\t\t},\n\t\t// Match register 1 with metadata server IP\n\t\t&expr.Cmp{\n\t\t\tOp:       expr.CmpOpEq,\n\t\t\tRegister: 1,\n\t\t\tData:     net.IPv4(169, 254, 169, 254).To4(),\n\t\t},\n\t\t// Load transport layer protocol into register 1\n\t\t&expr.Meta{\n\t\t\tKey:            expr.MetaKeyL4PROTO,\n\t\t\tSourceRegister: false,\n\t\t\tRegister:       1,\n\t\t},\n\t\t// Match transport layer protocol with TCP\n\t\t&expr.Cmp{\n\t\t\tOp:       expr.CmpOpEq,\n\t\t\tRegister: 1,\n\t\t\tData:     []byte{6}, // TCP\n\t\t},\n\t\t// Load network port into register 1\n\t\t&expr.Payload{\n\t\t\tOperationType:  expr.PayloadLoad,\n\t\t\tDestRegister:   1,\n\t\t\tSourceRegister: 0,\n\t\t\tBase:           expr.PayloadBaseTransportHeader,\n\t\t\tOffset:         2,\n\t\t\tLen:            2,\n\t\t\tCsumType:       expr.CsumTypeNone,\n\t\t\t//CsumOffset:     0,\n\t\t\t//CsumFlags:      0,\n\t\t},\n\t\t// Match register 1 with port 80\n\t\t&expr.Cmp{\n\t\t\tOp:       expr.CmpOpEq,\n\t\t\tRegister: 1,\n\t\t\tData:     []byte{0, 80},\n\t\t},\n\t\t// Adding a counter helps debugging\n\t\t&expr.Counter{\n\t\t\tBytes:   0,\n\t\t\tPackets: 0,\n\t\t},\n\t\t// Place destination IP in register 1\n\t\t&expr.Immediate{\n\t\t\tRegister: 1,\n\t\t\tData:     net.IPv4(127, 0, 0, 1).To4(),\n\t\t},\n\t\t// Place destination port in register 2\n\t\t&expr.Immediate{\n\t\t\tRegister: 2,\n\t\t\tData:     destinationPort,\n\t\t},\n\t\t&expr.NAT{\n\t\t\tType:        1,\n\t\t\tFamily:      2,\n\t\t\tRegAddrMin:  1,\n\t\t\tRegAddrMax:  1,\n\t\t\tRegProtoMin: 2,\n\t\t\tRegProtoMax: 2,\n\t\t\tRandom:      false,\n\t\t\tFullyRandom: false,\n\t\t\tPersistent:  false,\n\t\t},\n\t}\n\n\tcon.AddRule(&nftables.Rule{\n\t\tTable: table,\n\t\tChain: prerouting,\n\t\t// Flags:\n\t\tExprs:    e,\n\t\tUserData: metadataRule,\n\t})\n\n\tif err := con.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"nftables flush: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc removeNATRule() {\n\tcon, err := nftables.New()\n\tif err != nil {\n\t\tlog.Printf(\"Warning: nftables invocation failed: %v\", err)\n\t}\n\ttable := con.AddTable(&nftables.Table{\n\t\tName:   \"nat\",\n\t\tFamily: nftables.TableFamilyIPv4,\n\t})\n\taccept := nftables.ChainPolicyAccept\n\tprerouting := con.AddChain(&nftables.Chain{\n\t\tName:     \"PREROUTING\",\n\t\tTable:    table,\n\t\tHooknum:  nftables.ChainHookPrerouting,\n\t\tPriority: nftables.ChainPriorityNATDest,\n\t\tType:     nftables.ChainTypeNAT,\n\t\tPolicy:   &accept,\n\t})\n\trs, err := con.GetRules(table, prerouting)\n\tif err != nil {\n\t\tlog.Printf(\"Warning: nftables invocation failed: %v\", err)\n\t}\n\tfor _, r := range rs {\n\t\tif bytes.Equal(r.UserData, metadataRule) {\n\t\t\tcon.DelRule(r)\n\t\t}\n\t}\n\tif err := con.Flush(); err != nil {\n\t\tlog.Printf(\"Warning: nftables invocation failed: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/setup-dev/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/setup-dev\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/configutil:go_default_library\",\n        \"//src/go/pkg/kubeutils:go_default_library\",\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"//src/go/pkg/setup:go_default_library\",\n        \"//src/go/pkg/setup/util:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_client_go//dynamic:go_default_library\",\n        \"@io_k8s_client_go//tools/clientcmd:go_default_library\",\n        \"@io_k8s_client_go//tools/clientcmd/api:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n        \"@org_golang_x_oauth2//google:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"setup-dev\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/setup-dev/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/configutil\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubeutils\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/setup\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/setup/util\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tclientapi \"k8s.io/client-go/tools/clientcmd/api\"\n)\n\nconst robotPrefix = \"dev-\"\n\nvar (\n\tproject   = flag.String(\"project\", \"\", \"Project ID for the Google Cloud Platform\")\n\trobotName = flag.String(\"robot-name\", \"\", \"Robot name (default: select interactively)\")\n)\n\nfunc parseFlags() {\n\tflag.Parse()\n\n\tif *project == \"\" {\n\t\tfmt.Println(\"ERROR: --project not specified\")\n\t\tos.Exit(1)\n\t}\n}\n\nfunc main() {\n\tparseFlags()\n\n\tlogHandler := ilog.NewLogHandler(slog.LevelInfo, os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tctx := context.Background()\n\tf := &util.DefaultFactory{}\n\n\tvars, err := configutil.ReadConfig(*project)\n\tif err != nil {\n\t\tslog.Error(\"Failed to read config\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tdomain, ok := vars[\"CLOUD_ROBOTICS_DOMAIN\"]\n\tif !ok || domain == \"\" {\n\t\tdomain = fmt.Sprintf(\"www.endpoints.%s.cloud.goog\", *project)\n\t}\n\n\ttokenSource, err := google.DefaultTokenSource(context.Background(), \"https://www.googleapis.com/auth/cloud-platform\")\n\tif err != nil {\n\t\tslog.Error(\"Failed to create OAuth2 token source\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tk8sCfg := kubeutils.BuildCloudKubernetesConfig(tokenSource, domain)\n\tk8s, err := dynamic.NewForConfig(k8sCfg)\n\tif err != nil {\n\t\tslog.Error(\"Failed to create kubernetes client\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\trobotGVR := schema.GroupVersionResource{Group: \"registry.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"robots\"}\n\trobotClient := k8s.Resource(robotGVR).Namespace(\"default\")\n\n\t*robotName, err = setup.GetRobotName(ctx, f, robotClient, *robotName)\n\tif err != nil {\n\t\tslog.Error(\"Failed to get robot name\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tif err := createKubeRelayEntry(*project, domain, *robotName); err != nil {\n\t\tslog.Error(\"Failed to create kubectl context\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\t// TODO(ensonic): these are only used for the ssh-app\n\t// dev credentials are always created\n\tclient := oauth2.NewClient(context.Background(), tokenSource)\n\tif err := setupDevCredentials(client, domain, *robotName); err != nil {\n\t\tslog.Error(\"Failed to set up credentials\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tslog.Info(\"Setup complete.\")\n}\n\n// createKubeRelayEntry writes cluster configuration to ~/.kube/config,\n// pointing to the kubernetes-relay-server running on GKE and linked to the\n// given robot.\nfunc createKubeRelayEntry(projectID string, domain string, robotName string) error {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\tconfig, err := rules.Load()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif config.AuthInfos[\"cloud-robotics-gcp\"] == nil {\n\t\tconfig.AuthInfos[\"cloud-robotics-gcp\"] = &clientapi.AuthInfo{\n\t\t\tAuthProvider: &clientapi.AuthProviderConfig{Name: \"gcp\"},\n\t\t}\n\t}\n\tname := fmt.Sprintf(\"%s-robot\", projectID)\n\turl := fmt.Sprintf(\"https://%s/apis/core.kubernetes-relay/client/%s\", domain, robotName)\n\tif config.Clusters[name] == nil {\n\t\tconfig.Clusters[name] = &clientapi.Cluster{}\n\t}\n\t// Always overwrite the URL because it encodes the robot we're using.\n\tconfig.Clusters[name].Server = url\n\tif config.Contexts[name] == nil {\n\t\tconfig.Contexts[name] = &clientapi.Context{\n\t\t\tAuthInfo:  \"cloud-robotics-gcp\",\n\t\t\tCluster:   name,\n\t\t\tNamespace: \"default\",\n\t\t}\n\t}\n\tif err := clientcmd.WriteToFile(*config, rules.GetDefaultFilename()); err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Robot context created, use with:   kubectl --context %s\\n\", name)\n\treturn nil\n}\n\n// setupDevCredentials generates a workstation ID for use with the Token Vendor then\n// calls in to CreateAndPublishCredentialsToCloud to create and publish a private key.\nfunc setupDevCredentials(client *http.Client, domain string, robotName string) error {\n\thostname, err := os.Hostname()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to query hostname: %v\", err)\n\t}\n\tauth := &robotauth.RobotAuth{\n\t\tRobotName:           robotName,\n\t\tProjectId:           *project,\n\t\tDomain:              domain,\n\t\tPublicKeyRegistryId: makeIdentifier(hostname),\n\t}\n\tslog.Info(\"Creating new private key\")\n\tif err := auth.CreatePrivateKey(); err != nil {\n\t\treturn err\n\t}\n\tif err := setup.PublishCredentialsToCloud(client, auth /*retries*/, 1); err != nil {\n\t\treturn err\n\t}\n\tif err := auth.StoreInFile(); err != nil {\n\t\treturn fmt.Errorf(\"Failed to store private key: %v\", err)\n\t}\n\treturn nil\n}\n\n// makeIdentifier converts a string to a valid robot identifier by adding a prefix\n// and removing invalid characters.\nfunc makeIdentifier(base string) string {\n\tinvalid := regexp.MustCompile(\"[^a-zA-Z0-9_.~+%-]+\")\n\treturn robotPrefix + invalid.ReplaceAllString(base, \"\")\n}\n\nfunc containerExists(container string) (bool, error) {\n\tcmd := exec.Command(\"docker\", \"inspect\", container)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil && bytes.HasPrefix(output, []byte(\"Error: No such object\")) {\n\t\treturn false, nil\n\t} else if err != nil {\n\t\treturn false, fmt.Errorf(\"`docker inspect %s` failed (%v): %s\", container, err, output)\n\t}\n\treturn true, nil\n}\n\n// stopContainerIfNeeded stops a container if it exists, then waits for it to\n// be automatically deleted. It assumes the container was run with --rm.\nfunc stopContainerIfNeeded(container string) error {\n\tif exists, err := containerExists(container); err != nil {\n\t\treturn err\n\t} else if !exists {\n\t\treturn nil\n\t}\n\tslog.Info(\"Stopping container\", slog.String(\"Container\", container))\n\tcmd := exec.Command(\"docker\", \"stop\", container)\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"`docker stop %s` failed: %v\", container, err)\n\t}\n\t// Wait for the container to be deleted.\n\treturn backoff.Retry(\n\t\tfunc() error {\n\t\t\tif stillExists, err := containerExists(container); err != nil {\n\t\t\t\treturn backoff.Permanent(err)\n\t\t\t} else if stillExists {\n\t\t\t\treturn errors.New(\"container exists\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tbackoff.NewConstantBackOff(100*time.Millisecond),\n\t)\n}\n"
  },
  {
    "path": "src/go/cmd/setup-dev/setup-dev.md",
    "content": "# setup-dev command-line tool\n\nUse the `setup-dev` tool to connect your workstation to a robot. This tool will\nset up a Kubernetes context for connecting to a robot through Kubernetes relay.\n\n```bash\nsetup-dev --project my-project\n```\n"
  },
  {
    "path": "src/go/cmd/setup-robot/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\", \"go_test\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\", \"oci_push\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/setup-robot\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/configutil:go_default_library\",\n        \"//src/go/pkg/gcr:go_default_library\",\n        \"//src/go/pkg/kubeutils:go_default_library\",\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"//src/go/pkg/setup:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@com_github_spf13_pflag//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/validation:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_client_go//dynamic:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@org_golang_google_api//option:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\"main_test.go\"],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_client_go//dynamic/fake:go_default_library\",\n        \"@io_k8s_client_go//testing:go_default_library\",\n    ],\n)\n\npkg_tar(\n    name = \"base_image_with_files_layer\",\n    srcs = [\n        \"//src/app_charts/base:base-robot\",\n        \"//src/go/cmd/synk\",\n        \"@kubernetes_helm//:helm\",\n    ],\n    extension = \"tar.gz\",\n    package_dir = \"/setup-robot-files\",\n)\n\noci_image(\n    name = \"base_image_with_files\",\n    base = \"@distroless_cc\",\n    tars = [\":base_image_with_files_layer\"],\n)\n\ngo_binary(\n    name = \"setup-robot-app\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n)\n\npkg_tar(\n    name = \"setup-robot-image-layer\",\n    srcs = [\":setup-robot-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"setup-robot-image\",\n    base = \":base_image_with_files\",\n    entrypoint = [\"/setup-robot-app\"],\n    tars = [\":setup-robot-image-layer\"],\n    visibility = [\"//visibility:public\"],\n)\n\noci_push(\n    name = \"setup-robot.push\",\n    image = \":setup-robot-image\",\n    # repository is required, even in container_push\n    repository = \"TODO-registry\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/setup-robot/main.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/configutil\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/gcr\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubeutils\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/setup\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\tflag \"github.com/spf13/pflag\"\n\t\"golang.org/x/oauth2\"\n\t\"google.golang.org/api/option\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/validation\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n)\n\nvar (\n\trobotName          = new(string)\n\tproject            = flag.String(\"project\", \"\", \"Project ID for the Google Cloud Platform\")\n\trobotType          = flag.String(\"robot-type\", \"\", \"Robot type. Optional if the robot is already registered.\")\n\tregistryID         = flag.String(\"registry-id\", \"\", \"The ID used when writing the public key to the cloud registry. Default: robot-<robot-name>.\")\n\tlabels             = flag.String(\"labels\", \"\", \"Robot labels. Optional if the robot is already registered.\")\n\tannotations        = flag.String(\"annotations\", \"\", \"Robot annotations. Optional if the robot is already registered.\")\n\tcrSyncer           = flag.Bool(\"cr-syncer\", true, \"Set up the cr-syncer, and create a Robot CR in the cloud cluster.\")\n\tfluentd            = flag.Bool(\"fluentd\", true, \"Set up fluentd to upload logs to Stackdriver.\")\n\tfluentbit          = flag.Bool(\"fluentbit\", false, \"Set up fluentbit to upload logs to Stackdriver.\")\n\tlogPrefixSubdomain = flag.String(\"log-prefix-subdomain\", \"\", \"Subdomain to prepend to Fluentbit log tag prefix.\")\n\tdockerDataRoot     = flag.String(\"docker-data-root\", \"/var/lib/docker\", \"This should match data-root in /etc/docker/daemon.json.\")\n\tpodCIDR            = flag.String(\"pod-cidr\", \"192.168.9.0/24\",\n\t\t\"The range of Pod IP addresses in the cluster. This should match the CNI \"+\n\t\t\t\"configuration (eg Cilium's clusterPoolIPv4PodCIDR). If this is incorrect, \"+\n\t\t\t\"pods will get 403 Forbidden when trying to reach the metadata server.\")\n\trobotAuthentication = flag.Bool(\"robot-authentication\", true, \"Set up robot authentication.\")\n\trunningOnGKE        = flag.Bool(\"running-on-gke\", false, \"If running on GKE, skip setup steps that are unnecessary and will fail.\")\n\n\trobotGVR = schema.GroupVersionResource{\n\t\tGroup:    \"registry.cloudrobotics.com\",\n\t\tVersion:  \"v1alpha1\",\n\t\tResource: \"robots\",\n\t}\n)\n\nconst (\n\tfilesDir          = \"/setup-robot-files\"\n\thelmPath          = filesDir + \"/helm\"\n\tsynkPath          = filesDir + \"/synk\"\n\tnumDNSRetries     = 6\n\tnumServiceRetries = 6\n\t// commaSentinel is used when parsing labels or annotations.\n\tcommaSentinel = \"_COMMA_SENTINEL_\"\n\tbaseNamespace = \"default\"\n)\n\nfunc parseFlags() {\n\tflag.Usage = func() {\n\t\tfmt.Fprintln(os.Stderr, \"Usage: setup-robot <robot-name> --project <project-id> [OPTIONS]\")\n\t\tfmt.Fprintln(os.Stderr, \"  robot-name\")\n\t\tfmt.Fprintln(os.Stderr, \"        Robot name\")\n\t\tfmt.Fprintln(os.Stderr, \"\")\n\t\tflag.PrintDefaults()\n\t}\n\tflag.Parse()\n\n\tif flag.NArg() < 1 {\n\t\tflag.Usage()\n\t\tfmt.Println(\"ERROR: robot-name is required.\")\n\t\tos.Exit(1)\n\t} else if flag.NArg() > 1 {\n\t\tflag.Usage()\n\t\tfmt.Printf(\"ERROR: too many positional arguments (%d), expected 1.\", flag.NArg())\n\t\tos.Exit(1)\n\t} else if errs := validation.NameIsDNS1035Label(flag.Arg(0), false); len(errs) > 0 {\n\t\tfmt.Printf(\"ERROR: invalid cluster name %q: %s\", flag.Arg(0), strings.Join(errs, \", \"))\n\t\tos.Exit(1)\n\t}\n\n\t*robotName = flag.Arg(0)\n\n\tif *project == \"\" {\n\t\tflag.Usage()\n\t\tfmt.Println(\"ERROR: --project is required.\")\n\t\tos.Exit(1)\n\t}\n\tif *registryID == \"\" {\n\t\t*registryID = fmt.Sprintf(\"robot-%s\", *robotName)\n\t}\n\n\tif *fluentd && *fluentbit {\n\t\tflag.Usage()\n\t\tfmt.Println(\"ERROR: --fluentd and --fluenetbit cannot be enabled at the same time.\")\n\t\tos.Exit(1)\n\t}\n}\n\n// parseKeyValues splits a string on ',' and the entries on '=' to build a map.\nfunc parseKeyValues(s string) (map[string]string, error) {\n\tlset := map[string]string{}\n\n\tif s == \"\" {\n\t\treturn lset, nil\n\t}\n\n\t// To handle escaped commas, we replace them with a sentinel, then\n\t// restore them after splitting individual values.\n\ts = strings.ReplaceAll(s, \"\\\\,\", commaSentinel)\n\tfor _, l := range strings.Split(s, \",\") {\n\t\tl = strings.ReplaceAll(l, commaSentinel, \",\")\n\t\tparts := strings.SplitN(l, \"=\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, errors.New(\"not a key/value pair\")\n\t\t}\n\t\tlset[parts[0]] = parts[1]\n\t}\n\treturn lset, nil\n}\n\n// checkRobotName tests whether a Robot resource exists in the local cluster\n// with a different name. It is not safe to rerun setup-robot with a different\n// name as the chart-assignment-controller doesn't allow the clusterName field to change.\nfunc checkRobotName(ctx context.Context, client dynamic.Interface) error {\n\trobots, err := client.Resource(robotGVR).Namespace(\"default\").List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.Wrap(err, \"list local robots\")\n\t}\n\tfor _, r := range robots.Items {\n\t\tif r.GetName() != *robotName {\n\t\t\treturn fmt.Errorf(`this cluster was already set up with a different name. It is not safe to rename an existing cluster.\n    - either, use the old name:\n        setup_robot.sh %q [...]\n    - or, reset the cluster before renaming it:\n        sudo kubeadm reset\n\tsetup_robot.sh %q [...]`, r.GetName(), *robotName)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tparseFlags()\n\n\tlogHandler := ilog.NewLogHandler(slog.LevelInfo, os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\tctx := context.Background()\n\tenvToken := os.Getenv(\"ACCESS_TOKEN\")\n\tif envToken == \"\" {\n\t\tslog.Error(\"ACCESS_TOKEN environment variable is required.\")\n\t\tos.Exit(1)\n\t}\n\tregistry := os.Getenv(\"REGISTRY\")\n\tif registry == \"\" {\n\t\tslog.Error(\"REGISTRY environment variable is required.\")\n\t\tos.Exit(1)\n\t}\n\tparsedLabels, err := parseKeyValues(*labels)\n\tif err != nil {\n\t\tslog.Error(\"Invalid labels\", slog.String(\"Labels\", *labels), ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tparsedAnnotations, err := parseKeyValues(*annotations)\n\tif err != nil {\n\t\tslog.Error(\"Invalid annotations\", slog.String(\"Annotations\", *annotations), ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\t// Wait for in-cluster DNS to become available, otherwise\n\t// configutil.ReadConfig() may fail.\n\tif err := setup.WaitForDNS(\"storage.googleapis.com\", numDNSRetries); err != nil {\n\t\tslog.Error(\"Failed to resolve storage.googleapis.com. Please retry in 5 minutes.\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\t// Set up the OAuth2 token source.\n\ttokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: envToken})\n\n\tvars, err := configutil.ReadConfig(*project, option.WithTokenSource(tokenSource))\n\tif err != nil {\n\t\tslog.Error(\"Failed to read config for project\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tdomain, ok := vars[\"CLOUD_ROBOTICS_DOMAIN\"]\n\tif !ok || domain == \"\" {\n\t\tdomain = fmt.Sprintf(\"www.endpoints.%s.cloud.goog\", *project)\n\t}\n\n\t// Wait until we can resolve the project domain. This may require DNS propagation.\n\tif err := setup.WaitForDNS(domain, numDNSRetries); err != nil {\n\t\tslog.Error(\"Failed to resolve cloud cluster. Please retry in 5 minutes.\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\t// Connect to the surrounding k8s cluster.\n\tlocalConfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tslog.Error(\"Failed to load in-cluster config\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tk8sLocalClientSet, err := kubernetes.NewForConfig(localConfig)\n\tif err != nil {\n\t\tslog.Error(\"Failed to create kubernetes client set\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tif _, err := k8sLocalClientSet.AppsV1().Deployments(\"default\").Get(ctx, \"app-rollout-controller\", metav1.GetOptions{}); err == nil {\n\t\t// It's important to avoid deploying the cloud-robotics\n\t\t// metadata-server in the same cluster as the token-vendor,\n\t\t// otherwise we'll break auth for all robot clusters.\n\t\tslog.Error(\"The local context contains a app-rollout-controller deployment. It is not safe to run robot setup on a GKE cloud cluster.\")\n\t\tos.Exit(1)\n\t}\n\tk8sLocalDynamic, err := dynamic.NewForConfig(localConfig)\n\tif err != nil {\n\t\tslog.Error(\"Failed to create dynamic client set\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tif err := checkRobotName(ctx, k8sLocalDynamic); err != nil {\n\t\tslog.Error(\"RobotName\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\tif *robotAuthentication {\n\t\t// Set up robot authentication.\n\t\tauth := &robotauth.RobotAuth{\n\t\t\tRobotName:           *robotName,\n\t\t\tProjectId:           *project,\n\t\t\tDomain:              domain,\n\t\t\tPublicKeyRegistryId: *registryID,\n\t\t}\n\n\t\tslog.Info(\"Creating new private key\")\n\t\tif err := auth.CreatePrivateKey(); err != nil {\n\t\t\tslog.Error(\"Failed creating key\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\thttpClient := oauth2.NewClient(ctx, tokenSource)\n\t\tif err := setup.PublishCredentialsToCloud(httpClient, auth, numServiceRetries); err != nil {\n\t\t\tslog.Error(\"Failed to publish credentials.\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif err := auth.StoreInK8sSecret(ctx, k8sLocalClientSet, baseNamespace); err != nil {\n\t\t\tslog.Error(\"Failed to write auth secret\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif err := gcr.UpdateGcrCredentials(ctx, k8sLocalClientSet, auth); err != nil {\n\t\t\tslog.Error(\"Failed to update credentials\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tslog.Info(\"Initializing Synk\")\n\toutput, err := exec.Command(synkPath, \"init\").CombinedOutput()\n\tif err != nil {\n\t\tslog.Error(\"Synk init failed.\", ilog.Err(err), slog.String(\"Output\", string(output)))\n\t\tos.Exit(1)\n\t}\n\n\tappManagement := configutil.GetBoolean(vars, \"APP_MANAGEMENT\", true)\n\t// Use \"robot\" as a suffix for consistency for Synk deployments.\n\tinstallChartOrDie(ctx, k8sLocalClientSet, domain, registry, \"base-robot\", baseNamespace,\n\t\t\"base-robot-0.0.1.tgz\", appManagement)\n\n\t// Set up Robot CR as a last step (local CR needs CRD to be deployed)\n\tif *crSyncer {\n\t\t// Set up client for cloud k8s cluster, to create/update the Robot CR there.\n\t\tk8sCloudCfg := kubeutils.BuildCloudKubernetesConfig(tokenSource, domain)\n\t\tk8sCloudDynamic, err := dynamic.NewForConfig(k8sCloudCfg)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to create k8s client\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif err := createOrUpdateRobot(ctx, k8sCloudDynamic, parsedLabels, parsedAnnotations); err != nil {\n\t\t\tslog.Error(\"Failed to create/update cloud robot CR\", slog.String(\"Name\", *robotName), ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t} else {\n\t\t// Creating a Robot CR in the cloud would make the app-rollout-controller\n\t\t// create ChartAssignments in the cloud, but if the cr-syncer is disabled,\n\t\t// these would not be synced/installed.\n\t\t// Hence we create a local Robot CR to keep the same interface.\n\t\tif err := createOrUpdateRobot(ctx, k8sLocalDynamic, parsedLabels, parsedAnnotations); err != nil {\n\t\t\tslog.Error(\"Failed to create/update local robot CR\", slog.String(\"Name\", *robotName), ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tslog.Info(\"Setup complete\")\n}\n\nfunc helmValuesStringFromMap(varMap map[string]string) string {\n\tvarList := []string{}\n\tfor k, v := range varMap {\n\t\tvarList = append(varList, fmt.Sprintf(\"%s=%s\", k, v))\n\t}\n\treturn strings.Join(varList, \",\")\n}\n\n// installChartOrDie installs a chart using Synk.\nfunc installChartOrDie(ctx context.Context, cs *kubernetes.Clientset, domain, registry, name, namespace, chartPath string, appManagement bool) {\n\t// ensure namespace for chart exists\n\tif _, err := cs.CoreV1().Namespaces().Create(ctx,\n\t\t&corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: namespace,\n\t\t\t},\n\t\t},\n\t\tmetav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) {\n\t\tslog.Error(\"Failed to create namespace.\", slog.String(\"Namespace\", namespace), ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\tvars := helmValuesStringFromMap(map[string]string{\n\t\t\"domain\":               domain,\n\t\t\"registry\":             registry,\n\t\t\"project\":              *project,\n\t\t\"app_management\":       strconv.FormatBool(appManagement),\n\t\t\"cr_syncer\":            strconv.FormatBool(*crSyncer),\n\t\t\"fluentd\":              strconv.FormatBool(*fluentd),\n\t\t\"fluentbit\":            strconv.FormatBool(*fluentbit),\n\t\t\"log_prefix_subdomain\": *logPrefixSubdomain,\n\t\t\"docker_data_root\":     *dockerDataRoot,\n\t\t\"pod_cidr\":             *podCIDR,\n\t\t\"robot_authentication\": strconv.FormatBool(*robotAuthentication),\n\t\t\"running_on_gke\":       strconv.FormatBool(*runningOnGKE),\n\t\t\"robot.name\":           *robotName,\n\t})\n\tslog.Info(\"Installing chart using Synk\",\n\t\tslog.String(\"Chart\", name),\n\t\tslog.String(\"Path\", chartPath))\n\n\toutput, err := exec.Command(\n\t\thelmPath,\n\t\t\"template\",\n\t\t\"--set-string\", vars,\n\t\t\"--name\", name,\n\t\t\"--namespace\", namespace,\n\t\tfilepath.Join(filesDir, chartPath),\n\t).CombinedOutput()\n\tif err != nil {\n\t\tslog.Error(\"Synk install failed.\",\n\t\t\tslog.String(\"Chart\", name),\n\t\t\tilog.Err(err),\n\t\t\tslog.String(\"Helm output\", string(output)))\n\t\tos.Exit(1)\n\t}\n\tcmd := exec.Command(\n\t\tsynkPath,\n\t\t\"apply\",\n\t\tname,\n\t\t\"-n\", namespace,\n\t\t\"-f\", \"-\",\n\t)\n\t// Helm writes the templated manifests and errors alike to stderr.\n\t// So we can just take the combined output as is.\n\tcmd.Stdin = bytes.NewReader(output)\n\n\tif output, err = cmd.CombinedOutput(); err != nil {\n\t\tslog.Error(\"Synk install failed.\",\n\t\t\tslog.String(\"Chart\", name),\n\t\t\tilog.Err(err),\n\t\t\tslog.String(\"Synk output\", string(output)))\n\t\tos.Exit(1)\n\t}\n}\n\nfunc createOrUpdateRobot(ctx context.Context, k8sDynamicClient dynamic.Interface, labels map[string]string, annotations map[string]string) error {\n\tconst masterHost = \"cloudrobotics.com/master-host\"\n\tlabels[\"cloudrobotics.com/robot-name\"] = *robotName\n\thost := os.Getenv(\"HOST_HOSTNAME\")\n\tif host != \"\" && annotations[masterHost] == \"\" {\n\t\tannotations[masterHost] = host\n\t}\n\tcrc_version := os.Getenv(\"CRC_VERSION\")\n\tif crc_version != \"\" {\n\t\tannotations[\"cloudrobotics.com/crc-version\"] = crc_version\n\t}\n\trobotClient := k8sDynamicClient.Resource(robotGVR).Namespace(\"default\")\n\treturn setup.CreateOrUpdateRobot(ctx, robotClient, *robotName, *robotType, *project, labels, annotations)\n}\n"
  },
  {
    "path": "src/go/cmd/setup-robot/main_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\tregistry \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynfake \"k8s.io/client-go/dynamic/fake\"\n\tk8stest \"k8s.io/client-go/testing\"\n)\n\nfunc TestParseKeyValues_ReturnsEmptyMapOnEmptyInput(t *testing.T) {\n\t_, err := parseKeyValues(\"\")\n\tif err != nil {\n\t\tt.Errorf(\"Empty should be okay, but returned %v\", err)\n\t}\n}\n\nfunc TestParseKeyValues_HandlesSingleEntry(t *testing.T) {\n\tl, err := parseKeyValues(\"foo=bar\")\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse single entry, but returned %v\", err)\n\t}\n\tv, ok := l[\"foo\"]\n\tif !ok {\n\t\tt.Errorf(\"No 'foo' entry created\")\n\t}\n\tif v != \"bar\" {\n\t\tt.Errorf(\"labels['foo'] should be 'bar', but is %q\", v)\n\t}\n}\n\nfunc TestParseKeyValues_HandlesMultipleEntries(t *testing.T) {\n\tl, err := parseKeyValues(\"foo=bar,zoo=zar\")\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse single entry, but returned %v\", err)\n\t}\n\tv, ok := l[\"foo\"]\n\tif !ok {\n\t\tt.Errorf(\"No 'foo' entry created\")\n\t}\n\tif v != \"bar\" {\n\t\tt.Errorf(\"labels['foo'] should be 'bar', but is %q\", v)\n\t}\n\tv, ok = l[\"zoo\"]\n\tif !ok {\n\t\tt.Errorf(\"No 'zoo' entry created\")\n\t}\n\tif v != \"zar\" {\n\t\tt.Errorf(\"labels['zoo'] should be 'zar', but is %q\", v)\n\t}\n}\n\nfunc TestParseKeyValues_HandlesEscapedCommas(t *testing.T) {\n\tl, err := parseKeyValues(\"foo=bar\\\\,baz,zoo=zar\")\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse single entry, but returned %v\", err)\n\t}\n\tv, ok := l[\"foo\"]\n\tif !ok {\n\t\tt.Errorf(\"No 'foo' entry created\")\n\t}\n\tif v != \"bar,baz\" {\n\t\tt.Errorf(\"labels['foo'] should be 'bar,baz', but is %q\", v)\n\t}\n\tv, ok = l[\"zoo\"]\n\tif !ok {\n\t\tt.Errorf(\"No 'zoo' entry created\")\n\t}\n\tif v != \"zar\" {\n\t\tt.Errorf(\"labels['zoo'] should be 'zar', but is %q\", v)\n\t}\n}\n\nfunc TestParseKeyValues_HandlesSpaces(t *testing.T) {\n\tl, err := parseKeyValues(\"foo=bar baz\")\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse single entry, but returned %v\", err)\n\t}\n\tv, ok := l[\"foo\"]\n\tif !ok {\n\t\tt.Errorf(\"No 'foo' entry created\")\n\t}\n\tif v != \"bar baz\" {\n\t\tt.Errorf(\"labels['foo'] should be 'bar baz', but is %q\", v)\n\t}\n}\n\nfunc TestCheckRobotName_SucceedsWhenCRDNotFound(t *testing.T) {\n\tctx := context.Background()\n\tsc := runtime.NewScheme()\n\t*robotName = \"robot_name\"\n\n\tc := dynfake.NewSimpleDynamicClientWithCustomListKinds(sc,\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\trobotGVR: \"RobotList\",\n\t\t},\n\t)\n\t// In a fresh cluster, the Robot CRD doesn't exist, so GET robots\n\t// returns a 404.\n\tc.PrependReactor(\"list\", \"robots\", func(k8stest.Action) (bool, runtime.Object, error) {\n\t\treturn true, nil, &k8serrors.StatusError{metav1.Status{\n\t\t\tStatus:  metav1.StatusFailure,\n\t\t\tCode:    http.StatusNotFound,\n\t\t\tReason:  metav1.StatusReasonNotFound,\n\t\t\tMessage: \"the server could not find the requested resource\",\n\t\t}}\n\t})\n\terr := checkRobotName(ctx, c)\n\tif err != nil {\n\t\tt.Errorf(\"checkRobotName() failed unexpectedly: %v\", err)\n\t}\n}\n\nfunc TestCheckRobotName(t *testing.T) {\n\tctx := context.Background()\n\tsc := runtime.NewScheme()\n\tregistry.AddToScheme(sc)\n\t*robotName = \"robot_name\"\n\n\ttests := []struct {\n\t\tdesc      string\n\t\trobots    []runtime.Object\n\t\twantError bool\n\t}{\n\t\t{\n\t\t\tdesc:      \"empty cluster\",\n\t\t\trobots:    []runtime.Object{},\n\t\t\twantError: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"robot with same name\",\n\t\t\trobots: []runtime.Object{\n\t\t\t\t&unstructured.Unstructured{\n\t\t\t\t\tObject: map[string]interface{}{\n\t\t\t\t\t\t\"apiVersion\": \"registry.cloudrobotics.com/v1alpha1\",\n\t\t\t\t\t\t\"kind\":       \"Robot\",\n\t\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\t\"name\":      *robotName,\n\t\t\t\t\t\t\t\"namespace\": \"default\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantError: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"robot with other name\",\n\t\t\trobots: []runtime.Object{\n\t\t\t\t&unstructured.Unstructured{\n\t\t\t\t\tObject: map[string]interface{}{\n\t\t\t\t\t\t\"apiVersion\": \"registry.cloudrobotics.com/v1alpha1\",\n\t\t\t\t\t\t\"kind\":       \"Robot\",\n\t\t\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\t\t\"name\":      \"other_name\",\n\t\t\t\t\t\t\t\"namespace\": \"default\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantError: true,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tc := dynfake.NewSimpleDynamicClient(sc, tc.robots...)\n\t\t\terr := checkRobotName(ctx, c)\n\t\t\tif tc.wantError && err == nil {\n\t\t\t\tt.Errorf(\"checkRobotName() succeeded unexpectedly\")\n\t\t\t}\n\t\t\tif !tc.wantError && err != nil {\n\t\t\t\tt.Errorf(\"checkRobotName() failed unexpectedly: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCreateOrUpdateRobot_Succeeds(t *testing.T) {\n\tctx := context.Background()\n\thostname, err := os.Hostname()\n\tif err != nil {\n\t\tt.Fatal(\"Could not determine hostname\")\n\t}\n\tos.Setenv(\"HOST_HOSTNAME\", hostname)\n\n\tsc := runtime.NewScheme()\n\tregistry.AddToScheme(sc)\n\t*robotName = \"robot_name\"\n\n\ttests := []struct {\n\t\tdesc            string\n\t\tlabels          map[string]string\n\t\tannotations     map[string]string\n\t\trobot           *registry.Robot\n\t\twantLabels      map[string]string\n\t\twantAnnotations map[string]string\n\t}{\n\t\t{\n\t\t\tdesc:        \"other robot\",\n\t\t\tlabels:      map[string]string{},\n\t\t\tannotations: map[string]string{},\n\t\t\trobot: &registry.Robot{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"other_robot\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/robot-name\": \"robot_name\",\n\t\t\t},\n\t\t\twantAnnotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": hostname,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"robot without label\",\n\t\t\tlabels:      map[string]string{},\n\t\t\tannotations: map[string]string{},\n\t\t\trobot: &registry.Robot{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      *robotName,\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/robot-name\": \"robot_name\",\n\t\t\t},\n\t\t\twantAnnotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": hostname,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"robot with other label\",\n\t\t\tlabels:      map[string]string{},\n\t\t\tannotations: map[string]string{},\n\t\t\trobot: &registry.Robot{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      *robotName,\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tLabels:    map[string]string{\"cloudrobotics.com/ssh-port\": \"22\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/robot-name\": \"robot_name\",\n\t\t\t\t\"cloudrobotics.com/ssh-port\":   \"22\",\n\t\t\t},\n\t\t\twantAnnotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": hostname,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"robot with same hostname\",\n\t\t\tlabels:      map[string]string{},\n\t\t\tannotations: map[string]string{},\n\t\t\trobot: &registry.Robot{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        *robotName,\n\t\t\t\t\tNamespace:   \"default\",\n\t\t\t\t\tAnnotations: map[string]string{\"cloudrobotics.com/master-host\": hostname},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/robot-name\": \"robot_name\",\n\t\t\t},\n\t\t\twantAnnotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": hostname,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"robot with different hostname\",\n\t\t\tlabels:      map[string]string{},\n\t\t\tannotations: map[string]string{},\n\t\t\trobot: &registry.Robot{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        *robotName,\n\t\t\t\t\tNamespace:   \"default\",\n\t\t\t\t\tAnnotations: map[string]string{\"cloudrobotics.com/master-host\": \"other-host\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/robot-name\": \"robot_name\",\n\t\t\t},\n\t\t\twantAnnotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": hostname,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:   \"master-host given as input\",\n\t\t\tlabels: map[string]string{},\n\t\t\tannotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": \"correct-host\",\n\t\t\t},\n\t\t\trobot: &registry.Robot{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        *robotName,\n\t\t\t\t\tNamespace:   \"default\",\n\t\t\t\t\tAnnotations: map[string]string{\"cloudrobotics.com/master-host\": \"other-host\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/robot-name\": \"robot_name\",\n\t\t\t},\n\t\t\twantAnnotations: map[string]string{\n\t\t\t\t\"cloudrobotics.com/master-host\": \"correct-host\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tc := dynfake.NewSimpleDynamicClient(sc, tc.robot)\n\t\t\tif err := createOrUpdateRobot(ctx, c, tc.labels, tc.annotations); err != nil {\n\t\t\t\tt.Fatalf(\"createOrUpdateRobot() failed unexpectedly:  %v\", err)\n\t\t\t}\n\n\t\t\trobotClient := c.Resource(robotGVR).Namespace(\"default\")\n\t\t\trobot, err := robotClient.Get(ctx, *robotName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed getting robot: %v\", err)\n\t\t\t}\n\t\t\tgot, ok, err := unstructured.NestedStringMap(robot.Object, \"metadata\", \"labels\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed parsing robot labels: %v\", err)\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"robot %q is missing the label map\", *robotName)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tc.wantLabels) {\n\t\t\t\tt.Errorf(\"labels:\\n%q\\nwant:\\n%q\", got, tc.wantLabels)\n\t\t\t}\n\t\t\tgot, ok, err = unstructured.NestedStringMap(robot.Object, \"metadata\", \"annotations\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed parsing robot labels: %v\", err)\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"robot %q is missing the annotation map\", *robotName)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tc.wantAnnotations) {\n\t\t\t\tt.Errorf(\"annotations:\\n%q\\nwant:\\n%q\", got, tc.wantAnnotations)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/synk/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"synk.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/synk\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/synk:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@com_github_spf13_cobra//:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_cli_runtime//pkg/genericclioptions:go_default_library\",\n        \"@io_k8s_cli_runtime//pkg/resource:go_default_library\",\n        \"@io_k8s_client_go//dynamic:go_default_library\",\n        \"@io_k8s_client_go//plugin/pkg/client/auth:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"synk\",\n    embed = [\":go_default_library\"],\n    pure = \"on\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/synk/README.md",
    "content": "# synk\n\nsynk is a tool to sync manifests with a cluster.\n\nIt takes set of fully populated Kubernetes resources (files or in-process objects) and applies them to a cluster as a named collection. A custom resource is used to store which manifests are part of the set, to reliably cleanup resources that are no longer part of it. Custom Resource Definitions (CRDs) are properly initialized before any other resources are installed and resource dependencies are resolved through retry logic.\n\nIt has a similar intent to [Mortar](https://github.com/kontena/mortar), but synk is usable as a Golang library and has first-class support for CRDs.\n\nTo be compatible with some of the existing charts, we allow charts to install resources to \"kube-system\" as the only allowed namespace outside of the chart namespace.\n\n## Examples\n\n```\n# Apply my-chart.\nhelm template my-chart.tgz ... | synk apply my-chart -n default -f -\n\n# Remove my-chart.\nsynk delete my-chart.v1 -n default\n```\n\n## Behavior\n\nsynk's `apply` command works as follows:\n\n1. Resources are parsed from the input. Any namespaced resources that don't\n   specify a namespace already are updated with the value of the `--namespace`\n   (`-n`) flag.\n1. Any resources that specify a namespace other than `kube-system` or the given\n   namespace will cause synk to fail.\n1. synk creates a new `ResourceSet`, listing the resources that are to be\n   applied. If reapplying a previously applied set, it creates a new\n   ResourceSet with an incremented version number (eg `my-chart.v2`).\n1. The resources are split into two groups: CRDs and non-CRDs (\"regular\n   resources\").\n1. All CRDs are applied to the cluster. synk then waits for these to become\n   available.\n1. Next, regular resources are applied to the cluster.\n\n  - Updates: most resources are updated with PATCH requests, which reduces the\n    risk of resource version conflicts. Resources larger than 256kB, which can't\n    use an annotation to store the last-applied-configuration, are updated with\n    POST requests. Resources that can't be updated (eg Jobs, PersistentVolumes)\n    are deleted and recreated according to the `canReplace()` heuristic.\n\n  - Ownership: all resources specify the ResourceSet as via ownerReferences.\n    This means that the Kubernetes garbage collector will delete the resources\n    when the ResourceSet is deleted. Note that CRDs don't have ownerReferences,\n    as this presents a data loss risk: if the garbage collector deleted a CRD it\n    would also delete all corresponding CRs.\n\n  - Retries: if a transient error is encountered when applying any regular\n    resource, synk retries the failed resources until the number of failed\n    resources is stable. This retry loop exists to handle constraints of the\n    resource creation order: for example, a Pod must be created after the\n    ServiceAccount that it uses. This isn't relevant when using Deployments\n    instead of bare Pods, so in practice this retry loop is not essential,\n    although it could be useful when using CRDs and validation webhooks.\n\n1. The ResourceSet status is updated, describing for each resource whether it\n   was successfully applied or not.\n1. If all resources were successfully applied, any previous ResourceSets with\n   the same name are deleted. This means that if a resource is removed from a\n   chart, the Kubernetes garbage collector will delete it after synk\n   successfully reapplies the newer version of the chart.\n1. Finally, if this process failed due to a transient error (according to the\n   IsTransientErr() heuristic) and `--retries=0` hasn't been specified, `apply`\n   will be retried completely, including creation of a new ResourceSet. This\n   handles transient errors that take seconds or minutes to pass, such as\n   apiserver downtime.\n"
  },
  {
    "path": "src/go/cmd/synk/synk.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/synk\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/cli-runtime/pkg/resource\"\n\t\"k8s.io/client-go/dynamic\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n)\n\nconst (\n\tretryBackoff = 5 * time.Second\n)\n\nvar (\n\tmaxQPS  int\n\tretries uint64\n\n\tcmdRoot = &cobra.Command{\n\t\tUse:   \"synk\",\n\t\tShort: \"A tool to sync manifests with a cluster.\",\n\t}\n\tcmdInit = &cobra.Command{\n\t\tUse:   \"init\",\n\t\tShort: \"Initialize cluster for use with synk.\",\n\t\tRun:   runInit,\n\t}\n\tcmdApply = &cobra.Command{\n\t\tUse:   \"apply\",\n\t\tShort: \"Apply manifests to the cluster.\",\n\t\tRun:   runApply,\n\t}\n\tcmdDelete = &cobra.Command{\n\t\tUse:   \"delete\",\n\t\tShort: \"Delete all ResourceSets for the name.\",\n\t\tRun:   runDelete,\n\t}\n\n\trestOpts     = genericclioptions.NewConfigFlags(true)\n\tresourceOpts = genericclioptions.NewResourceBuilderFlags()\n)\n\nfunc main() {\n\tlogHandler := ilog.NewLogHandler(slog.LevelInfo, os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\trestOpts.AddFlags(cmdRoot.PersistentFlags())\n\tresourceOpts.AddFlags(cmdApply.PersistentFlags())\n\n\tcmdRoot.PersistentFlags().IntVar(&maxQPS, \"max-qps\", 50, \"max number of calls to the apiserver per second\")\n\tcmdApply.PersistentFlags().Uint64Var(&retries, \"retries\", 60, \"max number of retries for transient errors, with a 5 second constant backoff\")\n\n\tcmdRoot.AddCommand(cmdInit)\n\tcmdRoot.AddCommand(cmdApply)\n\tcmdRoot.AddCommand(cmdDelete)\n\n\tif err := cmdRoot.Execute(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc newSynk() (*synk.Synk, error) {\n\trestcfg, err := restOpts.ToRESTConfig()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"get config\")\n\t}\n\trestcfg.QPS = float32(maxQPS)\n\trestcfg.Burst = maxQPS * 2\n\tdiscovery, err := restOpts.ToDiscoveryClient()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"get discovery client\")\n\t}\n\tclient, err := dynamic.NewForConfig(restcfg)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"create dynamic client\")\n\t}\n\ts := synk.New(client, discovery)\n\n\t// Invalidate to be safe. It seems that a persistent discovery cache\n\t// likes to stay out of sync way too often.\n\tdiscovery.Invalidate()\n\n\treturn s, nil\n}\n\nfunc runInit(cmd *cobra.Command, args []string) {\n\ts, err := newSynk()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tif err := s.Init(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tfmt.Fprintln(os.Stderr, \"Initialized successfully\")\n}\n\nfunc runDelete(cmd *cobra.Command, args []string) {\n\tif len(args) != 1 {\n\t\tfmt.Fprintln(os.Stderr, \"unrecognized number of arguments, exactly one (name) expected\")\n\t\tos.Exit(2)\n\t}\n\ts, err := newSynk()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tif err := s.Delete(context.Background(), args[0]); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tfmt.Fprintln(os.Stderr, \"Deleted successfully\")\n}\n\nfunc runApply(cmd *cobra.Command, args []string) {\n\tif len(args) != 1 {\n\t\tfmt.Fprintln(os.Stderr, \"unrecognized number of arguments, exactly one (name) expected\")\n\t\tos.Exit(2)\n\t}\n\tif err := apply(args[0]); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc apply(name string) error {\n\t// If a target namesapce for the chart is given, enforce it.\n\tnamespace, enforceNamespace, err := restOpts.ToRawKubeConfigLoader().Namespace()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilenameOpts := resourceOpts.FileNameFlags.ToOptions()\n\n\tresult := resource.NewBuilder(restOpts).\n\t\tContinueOnError().\n\t\tUnstructured(). // Must be at the top.\n\t\tLocal().\n\t\tFilenameParam(false, &filenameOpts).\n\t\tFlatten().\n\t\tDo()\n\n\tif result.Err() != nil {\n\t\treturn errors.Wrap(result.Err(), \"get files\")\n\t}\n\tinfos, err := result.Infos()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"get file information\")\n\t}\n\tvar resources []*unstructured.Unstructured\n\tfor _, i := range infos {\n\t\tresources = append(resources, i.Object.(*unstructured.Unstructured))\n\t}\n\n\ts, err := newSynk()\n\tif err != nil {\n\t\treturn err\n\t}\n\topts := &synk.ApplyOptions{\n\t\tNamespace:        namespace,\n\t\tEnforceNamespace: enforceNamespace,\n\t\tLog:              logAction,\n\t}\n\tif err := backoff.Retry(\n\t\tfunc() error {\n\t\t\t_, err := s.Apply(context.Background(), name, opts, resources...)\n\t\t\tif err != nil {\n\t\t\t\tif synk.IsTransientErr(err) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn backoff.Permanent(err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(retryBackoff), retries),\n\t); err != nil {\n\t\treturn errors.Wrap(err, \"apply files\")\n\t}\n\treturn nil\n}\n\nfunc logAction(r *unstructured.Unstructured, action apps.ResourceAction, status, msg string) {\n\t// Remove some visual clutter by only showing the resource for successes.\n\tif status == synk.StatusSuccess {\n\t\tfmt.Fprintf(os.Stderr, \"[%s] %s %s/%s %s/%s\\n\",\n\t\t\tstrings.ToUpper(status), action,\n\t\t\tr.GetAPIVersion(), r.GetKind(),\n\t\t\tr.GetNamespace(), r.GetName(),\n\t\t)\n\t\treturn\n\t}\n\tfmt.Fprintf(os.Stderr, \"[%s] %s %s/%s %s/%s: %s\\n\",\n\t\tstrings.ToUpper(status), action,\n\t\tr.GetAPIVersion(), r.GetKind(),\n\t\tr.GetNamespace(), r.GetName(),\n\t\tmsg)\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_pkg//pkg:tar.bzl\", \"pkg_tar\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/api:go_default_library\",\n        \"//src/go/cmd/token-vendor/api/v1:go_default_library\",\n        \"//src/go/cmd/token-vendor/app:go_default_library\",\n        \"//src/go/cmd/token-vendor/oauth:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository/k8s:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository/memory:go_default_library\",\n        \"//src/go/cmd/token-vendor/tokensource:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//plugin/pkg/client/auth:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"token-vendor-app\",\n    embed = [\":go_default_library\"],\n)\n\npkg_tar(\n    name = \"token-vendor-image-layer\",\n    srcs = [\":token-vendor-app\"],\n    extension = \"tar.gz\",\n)\n\noci_image(\n    name = \"token-vendor-image\",\n    base = \"@distroless_base\",\n    entrypoint = [\"/token-vendor-app\"],\n    tars = [\":token-vendor-image-layer\"],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/README.md",
    "content": "# Token Vendor\n\nThe token vendor provides authentication for requests from the robots to our cloud environment.\nThe robots identity is generated during setup via a public-private key pair.\nThe token vendor provides APIs for registering robots through their public key and OAuth2 workflows for authenticating the signed requests from robots to cloud resources, for example to write logs to GCP Logging.\nThe token vendor itself is stateless and all data is stored in GCP.\n\nThe following workflows are covered by the token vendor:\n\n* Register a robot by its public key and a unique device identifier. The public key is stored in a cloud backend.\n* Retrieve a robot's public key through the device identifier\n* Generate an scoped and time-limited IAM access token for access to GCP resources\n* Validate a given IAM access token\n\n## Public Key Backends\n\nThe token vendor supports multiple backends for storage of public keys for registered devices.\n\n### Kubernetes Configmaps\n\nThe Kubernetes backend uses configmaps to store and lookup public keys.\nThe configmaps are stored in a configured namespace with the device identifier as name.\nThe public key is stored under a key in the configmap.\nDevices can be removed by deleting the configmap.\n\n### In-Memory\n\nStores public keys in-memory for testing.\n\nExample:\n\n```\n# Run with memory backend\nbazel run //src/go/cmd/token-vendor -- -verbose --project testproject --accepted_audience test --key-store IN_MEMORY\n# Store test key\ncurl --data-binary \"@api/v1/testdata/rsa_cert.pem\" -H \"Content-type: application/x-pem-file\" -D - http://127.0.0.1:9090/apis/core.token-vendor/v1/public-key.publish?device-id=robot-dev-testuser\n# Retrieve key\ncurl -D - http://127.0.0.1:9090/apis/core.token-vendor/v1/public-key.read?device-id=robot-dev-testuser\n```\n\n## API\n\n### /public-key.publish: Robot registration\n\nNew robots get registered by a human administrator (authorized by an access\ntoken on the request). The method add the provided public key to the configured\nkey store. Write access to the public key registry needs to be restricted to\nrefuse eg. robots to register other robots.\n\n* URL: /apis/core.token-vendor/v1/public-key.publish\n* Method: POST\n* URL Params:\n  * device-id: unique device name (by default robot-<robot-id>)\n* Body: application/x-pem-file\n* Response: only http status code\n\n### /public-key.configure: Customize robot registration\n\nConfigure optional properties of the on-prem robot registration. This call needs\nto be authorized by an access token for a human administrator).\n\n* URL: /apis/core.token-vendor/v1/public-key.configure\n* Method: POST\n* URL Params:\n  * device-id: unique device name (by default robot-<robot-id>)\n* Body: json {\n  service-account: str, defaults to robot-service@<gcp-project>.iam.gserviceaccount.com\"\n  service-account-delegate: str, optional intermediate delegate\n}\n* Response: only http status code\n\n### /public-key.read: Public key retrieval\n\nTo verify messages send by a robot one can fetch the public key from the\nkeystore using this method.\n\n* URL: /apis/core.token-vendor/v1/public-key.read\n* Method: GET\n* URL Params:\n  * device-id: unique device name (by default robot-<robot-id>)\n* Response: application/x-pem-file\n\n### /token.oauth2: OAuth2 access token requests by robots\n\nRobots sign JWTs with their local private keys. These get verified against the\npublic keys from the keystore. If the key is present and enabled, the token\nvendor will hand out an OAuth access token for requested service account.\nThe service account must be either the default one (robot-service@) or the\naccount configured during registration (see /public-key.configure). To specify\ncustom service account use Subject claim.\n\n* URL: /apis/core.token-vendor/v1/token.oauth2\n* Method: POST\n* Body: JWT query (TokenSource)\n* Response: application/json\n\n### /token.verify: AuthN/Z verification\n\nBrowsers or robots can query endpoints like the ws-proxy with authorization\nheaders or a `?token=` query parameter. They are already authenticated, and the\ntoken vendor just checks that IAM authorizes the request.\n\n* URL: /apis/core.token-vendor/v1/token.verify\n* Method: GET\n* URL Params:\n  * robots: boolean to indicate if robot-service account tookens are allowed\n* Response: only http status code\n\nResults are backed by a cache with a 5 minute lifetime to ease the load on the\nIAM backend.\n\n### /jwt.verify: AuthZ verification\n\nRobots sign JWTs with their local private keys. These get verified against the\npublic keys from the keystore. If the key is present and enabled, the token\nvendor will return status code 200.\nThis endpoint allows 3rd parties to do a check against the token-vendor before\nthe client reached the token vendor to retrieve an OAuth token.\nIt only validates whether the robot is known to the token vendor, there is no\nfurther authentication or authorization done with this endpoint.\n\n* URL: /apis/core.token-vendor/v1/jwt.verify\n* Method: GET\n* Headers:\n  * Authorization: JWT that allows authorization\n* Response: only http status code\n\n## Interactive AuthN & AuthZ (with oauth2-proxy)\n\nWe use the token vendor together with oauth2-proxy as an authentication and\nauthorization helper for nginx. This is essentially a poor man's IAP, used\nbecause the GCE Ingress controller does not support IAP annotations on the GCE\nobjects it creates. Ingresses can be protected by it with an auth-url\nannotation:\n\n```\nnginx.ingress.kubernetes.io/auth-url: \"http://oauth2-proxy.default.svc.cluster.local/apis/core.token-vendor/v1/token.verify\"\nnginx.ingress.kubernetes.io/auth-signin: \"https://{{ .Values.domain }}/oauth2/start?rd=$escaped_request_uri\"\n```\n\nnginx is set up to use the oauth2-proxy as an authentication proxy, and the\noauth2-proxy has its upstream set to the token vendor.\n\nThe request for unauthenticated users flows like this:\n\n 1. nginx-ingress receives the request.\n 1. nginx-ingress queries the auth-url.\n 1. oauth2-proxy sees that there's no cookie attached, redirects the user to the\n    Google sign-in page.\n 1. When the user is signed in, oauth2-proxy's callback page sets the encrypted\n    auth cookie and redirects the user back to the original URL.\n 1. nginx-ingress again queries the auth-url.\n 1. oauth2-proxy sees the auth cookie, decrypts it and forwards the access token\n    to the token vendor.\n 1. The token vendor sees the access token and checks the authorization with\n    IAM.\n 1. When authorization is fine, token vendor returns 200.\n 1. nginx-ingress forwards the request to the backend.\n\n## curl API Example Workflow\n\nFirst set the name of your project:\n\n```bash\nPROJECT=testproject\n```\n\nPublish a key for the device `robot-dev-testuser`:\n\n```bash\ncurl -D - --max-time 3 --data-binary \"@api/v1/testdata/rsa_cert.pem\" -H \"Authorization: Bearer $(gcloud auth print-access-token)\" -H \"Content-type: application/x-pem-file\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/public-key.publish?device-id=robot-dev-testuser\n```\n\nOptionally set extra options for the device:\n\n```bash\ncurl -D - --max-time 3 -d '{\"service-account\":\"svc@${PROJECT}.iam.gserviceaccount.com\"}' -H \"Content-Type: application/json\" -H \"Authorization: Bearer $(gcloud auth print-access-token)\" -H \"Content-type: application/x-pem-file\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/public-key.configure?device-id=robot-dev-testuser\n```\n\nRead the key again:\n\n```bash\ncurl -D - --max-time 3 -H \"Authorization: Bearer $(gcloud auth print-access-token)\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/public-key.read?device-id=robot-dev-testuser\n```\n\nVerify if your local user account has access to the human and robot ACL:\n\n```bash\ncurl -D - --max-time 3 -H \"Authorization: Bearer $(gcloud auth print-access-token)\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify\n```\n\nand\n\n```bash\ncurl -D - --max-time 3 -H \"Authorization: Bearer $(gcloud auth print-access-token)\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify?robots=true\n```\n\nRequest a cloud access token for the robot. First generate a valid JWT using the intstructions at [testdata/README.md](api/v1/testdata/README.md). Afterwards use it to request the cloud token:\n\n```bash\nJWT=$(cat api/v1/testdata/jwt.bin)\ncurl -D - --max-time 3 --data-binary \"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${JWT}\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.oauth2\n```\n\nYou can capture the token in `$TOKEN` with:\n\n```bash\nTOKEN=$(curl -s --max-time 3 --data-binary \"grant_type=urn:ietf:params:oauth:grant-typ\ne:jwt-bearer&assertion=${JWT}\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.oauth2 | jq -r .access_token)\n```\n\nVerify if the token has access to the robots ACL (it should respond 200):\n\n```bash\ncurl -D - --max-time 3 -H \"Authorization: Bearer ${TOKEN}\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify?robots=true\n```\n\nVerify if the token does *not* have access to the human ACL (it should respond 403):\n\n```bash\ncurl -D - --max-time 3 -H \"Authorization: Bearer ${TOKEN}\" https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.verify\n```\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"api.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/api\",\n    visibility = [\"//visibility:public\"],\n    deps = [\"@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library\"],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/api.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage api\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\nconst (\n\thttpTimeoutRead    = 10 * time.Second\n\thttpTimeoutWrite   = 10 * time.Second\n\thttpTimeoutHandler = 10 * time.Second\n)\n\ntype constHandler []byte\n\nfunc (ch constHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Metadata-Flavor\", \"Google\")\n\tw.Header().Set(\"Content-Type\", \"application/text\")\n\tw.WriteHeader(http.StatusOK)\n\tw.Write(ch)\n}\n\n// Register generic API API handlers functions to the default http.DefaultServeMux\nfunc Register() error {\n\thttp.Handle(\"/healthz\", constHandler(\"ok\"))\n\thttp.Handle(\"/metrics\", promhttp.Handler())\n\treturn nil\n}\n\n// Setup and serve. Never returns. Handlers need to be registered before.\nfunc SetupAndServe(addr string) error {\n\tsrv := &http.Server{\n\t\tAddr:         addr,\n\t\tReadTimeout:  httpTimeoutRead,\n\t\tWriteTimeout: httpTimeoutWrite,\n\t\tHandler: http.TimeoutHandler(LoggingMiddleware(http.DefaultServeMux),\n\t\t\thttpTimeoutHandler, \"handler timeout\"),\n\t}\n\tslog.Info(\"API listening\", slog.String(\"Address\", addr))\n\treturn srv.ListenAndServe()\n}\n\nfunc LoggingMiddleware(handler http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\txFwd := r.Header.Get(\"X-Forwarded-For\")\n\t\tslog.Debug(\"Forwarding request\",\n\t\t\tslog.String(\"RemoteAddr\", r.RemoteAddr),\n\t\t\tslog.String(\"For\", xFwd),\n\t\t\tslog.String(\"Method\", r.Method),\n\t\t\tslog.String(\"URL\", r.URL.String()))\n\t\thandler.ServeHTTP(w, r)\n\t})\n}\n\n// ErrResponse reports the statusCode and message as a http response and also\n// logs it together with the given details.\nfunc ErrResponse(ctx context.Context, w http.ResponseWriter, statusCode int, message string, details ...slog.Attr) {\n\tfinalAttrs := append([]slog.Attr{slog.Group(\"error\",\n\t\tslog.Int(\"code\", statusCode),\n\t\tslog.String(\"message\", message),\n\t)}, details...)\n\tslog.LogAttrs(ctx, slog.LevelWarn, \"Error response\", finalAttrs...)\n\tw.WriteHeader(statusCode)\n\tw.Write([]byte(message))\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"v1.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/api/v1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/api:go_default_library\",\n        \"//src/go/cmd/token-vendor/app:go_default_library\",\n        \"//src/go/cmd/token-vendor/oauth:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"v1_test.go\"],\n    data = glob([\"testdata/**\"]),\n    embed = [\":go_default_library\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/app:go_default_library\",\n        \"//src/go/cmd/token-vendor/oauth:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository/k8s:go_default_library\",\n        \"//src/go/cmd/token-vendor/tokensource:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/fake:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/testdata/README.md",
    "content": "# Testdata\n\n## RSA Keys\n\nThese files were generated like this:\n\n```shell\nopenssl genrsa -out rsa_private.pem 2048\nopenssl rsa -in rsa_private.pem -pubout -out rsa_cert.pem\n```\n\n## Create a test JWT\n\nGo to [jwt.io](https://jwt.io/). Use the following header:\n\n```json\n{\n\t\"alg\": \"RS256\",\n\t\"typ\": \"JWT\"\n}\n```\n\nUse the following payload, replace `${PROJECT}` with your cloud project identifier and update the expire timestamp:\n\n```json\n{\n\t\"aud\": \"https://www.endpoints.${PROJECT}.cloud.goog/apis/core.token-vendor/v1/token.oauth2\",\n\t\"iss\": \"robot-dev-testuser\",\n\t\"exp\": 1913373010,\n\t\"scopes\": \"unused\",\n\t\"claims\": \"unused\"\n}\n```\n\nUse `rsa_cert.pem` and `rsa_private.pem` as keys.\n\n\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/testdata/cloudiot/describe_device.json",
    "content": "{\n    \"id\": \"robot-dev-device\",\n    \"name\": \"projects/testproject/locations/europe-west1/registries/cloud-robotics/devices/3072877074145970\",\n    \"numId\": \"3072877074145970\",\n    \"credentials\": [\n      {\n        \"publicKey\": {\n          \"format\": \"RSA_PEM\",\n          \"key\": \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQAB\\n-----END PUBLIC KEY-----\"\n        },\n        \"expirationTime\": \"1970-01-01T00:00:00Z\"\n      }\n    ],\n    \"config\": {\n      \"version\": \"1\",\n      \"cloudUpdateTime\": \"2022-08-18T15:36:53.627428Z\"\n    },\n    \"gatewayConfig\": {}\n  }"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/testdata/cloudiot/describe_device_expired_key.json",
    "content": "{\n    \"id\": \"robot-dev-device\",\n    \"name\": \"projects/testproject/locations/europe-west1/registries/cloud-robotics/devices/3072877074145970\",\n    \"numId\": \"3072877074145970\",\n    \"credentials\": [\n      {\n        \"publicKey\": {\n          \"format\": \"RSA_PEM\",\n          \"key\": \"an_expired_key\"\n        },\n        \"expirationTime\": \"1990-01-01T00:00:00Z\"\n      }\n    ],\n    \"config\": {\n      \"version\": \"1\",\n      \"cloudUpdateTime\": \"2022-08-18T15:36:53.627428Z\"\n    },\n    \"gatewayConfig\": {}\n  }"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/testdata/rsa_cert.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C\n2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sM\ngyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxX\nvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmz\nQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard\n17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xD\npwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/testdata/rsa_private.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3\nOJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/\nyfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pk\ndXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22\naCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsF\neZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQABAoH/bKMLrT/W4/wT+6PN\nKU3FVbWDompywyssqlZ31Q6g9pdCCTIyw0jemlG0ewtdk3yIu8WS0Aku36NudWtP\npvDBPo+CZILRYS9N0AUNXBPl7sUA4OzVdCBnk5FTF1daV7N5CA+ZDXuDVa91fduJ\n1ElSF9+weCKph0170Rsc74G570Q1ypoee/gdhkwwK5aYfTs+Z6fpaEnHaPzcwYkF\n4QTsCshtoGZslmgZt8Tm7sfDDFWD20fmr1s350Ne1I7VYRFiyGbQI+IB+4pc9LSX\n8CHcHIzHidKYTSG6YwpDsNRN/BkQklhsuLnNacMFFddO0IHIS0GlLBJbCRkN3b/n\n/XC5AoGBAPZIN3VCpSEAw6OsM1zL4CBcq2dOb5b87rAeUmSkmW415fuyUNJJBcaf\n1pliCQNeg9RzRDuHOs6BTU9i+fLcbOwSapFzGxzqnv4xmkHbj1Xs52Z+97HvKKld\nxlQ/TF72WGITZVwmQWxJ9Rgx+bi7OirzOtQYoNpFoF5vHgyGrUZ7AoGBAMSosXUk\nuLMzrZjH4Oetp8tq9Udyk7Xkk7booU7I0iPb/Dvadsuc9WZI+LP4R3iWmtLcJOUr\nWyfliCLvbWtF4aW2vo7hvffe19krg/H26WEuBTuQGCZv8B5o8xHSecb7jbrKt9g6\nr8I5kr+2tAZKLC6mtFdJgfSXNO9tveBxe+XFAoGBAIwQljnCJVeXr6wuCygDavv8\nuB6QpTYhsz3GgOVsFzZuwNVcnEp77SUBUnL5JlccMa1pwKx6RB+dufIkQDK22duI\nvcLqy8iuRq4aV7iMvgAIM7I/E2/GrEFma50OQsjfIXTlwwedWifUB+gyw+sjz/kN\nS6/EMfbxEjuixlwpW/JxAoGBAKG5dM44F6hPPFijL0J3XcD8QZ+zCuQPiKZnopgO\nsDmLJF/4Za9Gccze/5/I8sWpXMNBBRptUDZ8HTtVmK8aNdm4cfdAj5/y46EVlxl6\nCyy+0tDLzAB4F4h6mEI0y66mmkRdh1jL0lQwUo1Ua7Gsd68Zqr8JlVSWsJKhtf+I\nc/JdAoGAFCSDby7ByX0W23Su3R28+9lWRSmNG79kLRLzlXsCwXTUTFh/TjAaEKgK\nvwi8dtCSMNnJLCUXGx5cjTndgjTl8Woah0wy9XNNeIUjI8JPxIwXmmjppPKdCBI4\n0ZyqQjgPJvwfY7lxFjE10ypv99QDlEbnwngt6bvSkY+6+DQTUDw=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/v1.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage v1\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/api\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/app\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n\t\"github.com/googlecloudrobotics/ilog\"\n)\n\nconst (\n\tparamDeviceID = \"device-id\"\n\tcontentType   = \"content-type\"\n\tpemFile       = \"application/x-pem-file\"\n)\n\ntype HandlerContext struct {\n\ttv *app.TokenVendor\n}\n\nfunc NewHandlerContext(tv *app.TokenVendor) *HandlerContext {\n\treturn &HandlerContext{tv}\n}\n\n// getQueryParam extracts a query parameter from the request URL.\n//\n// Multiple parameters with the same key are considered undefined and will result in error\nfunc getQueryParam(u *url.URL, param string) (string, error) {\n\tvalues, ok := u.Query()[param]\n\tif !ok || len(values) != 1 {\n\t\terr := fmt.Errorf(\"missing or multiple query parameter %s\", param)\n\t\treturn \"\", err\n\t}\n\treturn values[0], nil\n}\n\n// Handle requests to configure optional properties of the device registration.\n//\n// Method: POST\n// URL parameter: device-id, the string identifier of the device\n//\n//\tBody: json {\n//\t service-account: str, defaults to robot-service@<gcp-project>.iam.gserviceaccount.com\"\n//\t service-account-delegate: str, optional intermediate delegate\n//\t}\n//\n// Response code: only http status code\nfunc (h *HandlerContext) publicKeyConfigureHandler(w http.ResponseWriter, r *http.Request) {\n\t// validate request and parameters\n\tif r.Method != http.MethodPost {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(\"method %s not allowed, only %s\", r.Method, http.MethodPost))\n\t\treturn\n\t}\n\tdeviceID, err := getQueryParam(r.URL, paramDeviceID)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tif !app.IsValidDeviceID(deviceID) {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, \"invalid device id\", slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"failed to read request body\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\n\tvar opts repository.KeyOptions\n\tif err := json.Unmarshal([]byte(body), &opts); err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, \"invalid body\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\n\tif err := h.tv.ConfigurePublicKey(r.Context(), deviceID, opts); err != nil {\n\t\tif errors.Is(err, repository.ErrNotFound) {\n\t\t\tapi.ErrResponse(r.Context(), w, http.StatusNotFound, \"request to repository failed\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\t} else {\n\t\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"request to repository failed\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\t}\n\t}\n}\n\n// Handle requests to read a device's public key by device identifier.\n//\n// Method: GET\n// URL parameter: device-id, the string identifier of the device\n//\n// Response code: 200 if key has been found\n// Response body: A single public key or \"\" on error.\nfunc (h *HandlerContext) publicKeyReadHandler(w http.ResponseWriter, r *http.Request) {\n\t// validate request and parameters\n\tif r.Method != http.MethodGet {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(\"method %s not allowed, only %s\", r.Method, http.MethodGet))\n\t\treturn\n\t}\n\tdeviceID, err := getQueryParam(r.URL, paramDeviceID)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tif !app.IsValidDeviceID(deviceID) {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, \"invalid device id\", slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\t// retrieve public key from key repository\n\tpublicKey, err := h.tv.ReadPublicKey(r.Context(), deviceID)\n\tif err != nil {\n\t\tif errors.Is(err, repository.ErrNotFound) {\n\t\t\tapi.ErrResponse(r.Context(), w, http.StatusNotFound, \"request to repository failed\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\t} else {\n\t\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"request to repository failed\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\t}\n\t\treturn\n\t}\n\t// for missing public keys (publicKey == \"\") we return 200 with\n\t// empty body for conformance with the original token vendor API.\n\tw.Header().Add(contentType, pemFile)\n\tw.Write([]byte(publicKey))\n}\n\n// Handle requests to publish the public key of a given device identifier.\n//\n// Method: POST\n// URL parameter: device-id, the identifier of the device\n// Request body: a single public key to publish\n// Response code: 200 if publish succeeded\nfunc (h *HandlerContext) publicKeyPublishHandler(w http.ResponseWriter, r *http.Request) {\n\t// validate request and parameters\n\tif r.Method != http.MethodPost {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(\"method %s not allowed, only %s\", r.Method, http.MethodPost))\n\t\treturn\n\t}\n\tdeviceID, err := getQueryParam(r.URL, paramDeviceID)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tif !app.IsValidDeviceID(deviceID) {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, \"invalid device id\", slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"failed to read request body\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\t_, err = isValidPublicKey(body)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, \"public key format error\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n\t// publish the key\n\terr = h.tv.PublishPublicKey(r.Context(), deviceID, string(body))\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"publish key failed\", ilog.Err(err), slog.String(\"DeviceID\", deviceID))\n\t\treturn\n\t}\n}\n\n// isValidPublicKeyFormat validates the given public key in PEM format.\n//\n// The returned error provides details on why the validation failed.\nfunc isValidPublicKey(pk []byte) (bool, error) {\n\tconst minSize, maxSize = 100, 18000 // educated guesses, technically unlimited\n\tif len(pk) < minSize || len(pk) > maxSize {\n\t\treturn false, fmt.Errorf(\"invalid key size, assert %d <= %d <= %d\", minSize, len(pk), maxSize)\n\t}\n\tpk = bytes.TrimSpace(pk)\n\tvar pkStart = []byte(\"-----BEGIN \")\n\tif !bytes.HasPrefix(pk, pkStart) {\n\t\treturn false, fmt.Errorf(\"public key suffix %q missing\", pk)\n\t}\n\tblock, extraData := pem.Decode(pk)\n\tif len(extraData) > 0 {\n\t\treturn false, fmt.Errorf(\"public key contains extra data (%d Bytes)\", len(extraData))\n\t}\n\tif block == nil || block.Type != \"PUBLIC KEY\" {\n\t\treturn false, fmt.Errorf(\"failed to decode PEM block expecting public key\")\n\t}\n\treturn true, nil\n}\n\n// Handle requests to retrieve a GCP access token for access to cloud resources.\n//\n// The robot identifies itself using its private key to sign a JWT. The JWT is\n// verified by the token vendor using the public key from the key repository and\n// the device identifier from the `iss` key from the JWT's payload section.\n// After successful verification, the token vendor uses its own IAM identity to\n// generate an access token for the `robot-service` service account and returns\n// the access token to the robot. We only accept RSA as signing algorithm right\n// now. This is the default used by the metadata server's oauth package [1].\n//\n// [1] https://github.com/golang/oauth2/blob/f21342109be17cd214ecfcd33065b79cd571673e/jwt/jwt.go#L29\n//\n// Method: POST\n// The body is formatted like an URL query with two parameters:\n//   - grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer\n//   - assertion=<jwt signed by robots private key>\n//\n// Signed JWT expected header:\n// “`json\n// {\n//\n//\t\"alg\": \"RS256\",\n//\t\"typ\": \"JWT\"\n//\n// }\n// “`\n// The JWT body is expected to look like this:\n// “`json\n// {\n//\n//\t\"aud\": \"<accepted audience>\" or \"<accepted audience>?token_type=access_token\", // has to match the one specified in the token vendor config\n//\t\"iss\": \"<device identifier>\",\n//\t\"exp\": <expiration timestamp>,\n//\t\"scopes\": \"...\",  // unused\n//\t\"claims\": \"...\" // unused\n//\n// }\n// The response body will look like this:\n// \"`json\n// {\n//\n//\t\"access_token\":\"foo\", // the cloud access token\n//\t\"expires_in\":3600, // the cloud access token expiration in seconds from now\n//\t\"scope\":\"http://example1.com http://example2.com\", // GCP API scope URLs from token vendor config\n//\t\"token_type\":\"Bearer\" // static\n//\n// }\n// “`\nfunc (h *HandlerContext) tokenOAuth2Handler(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(\"method %s not allowed, only %s\", r.Method, http.MethodPost))\n\t\treturn\n\t}\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"error reading request body\", ilog.Err(err))\n\t\treturn\n\t}\n\tvalues, err := url.ParseQuery(string(body))\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tconst paramGrant = \"grant_type\"\n\tconst jwtGrant = \"urn:ietf:params:oauth:grant-type:jwt-bearer\"\n\tgrant := values.Get(paramGrant)\n\tif grant != jwtGrant {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(`expected \"%s=%s\" in body`, paramGrant, jwtGrant))\n\t\treturn\n\t}\n\tconst paramAssert = \"assertion\"\n\tassertion := values.Get(paramAssert)\n\tif _, err := isValidJWT(assertion); err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(`expected \"%s=<jwt>\" in body, invalid token format: %v`, paramAssert, err))\n\t\treturn\n\t}\n\ttoken, err := h.tv.GetOAuth2Token(r.Context(), assertion)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusForbidden, \"unable to retrieve cloud access token with given JWT\", ilog.Err(err))\n\t\treturn\n\t}\n\ttokenBytes, err := json.Marshal(token)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusInternalServerError, \"failed to marshal upstream response\", ilog.Err(err))\n\t\treturn\n\t}\n\tw.Header().Add(contentType, \"application/json\")\n\tw.Write(tokenBytes)\n}\n\n// Robots sign JWTs with their local private keys. These get verified against the\n// public keys from the keystore. If the key is present and enabled, the token\n// vendor will return status code 200.\n// This endpoint allows 3rd parties to do a check against the token-vendor before\n// the client reached the token vendor to retrieve an OAuth token.\n// It only validates whether the robot is known to the token vendor, there is no\n// further authentication or authorization done with this endpoint.\n//\n// URL: /apis/core.token-vendor/v1/jwt.verify\n// Method: GET\n// Headers:\n// - Authorization: JWT that allows authorization\n// Response: only http status code\nfunc (h *HandlerContext) verifyJWTHandler(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodGet {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusMethodNotAllowed,\n\t\t\tfmt.Sprintf(\"method %s not allowed, only %s\", r.Method, http.MethodGet))\n\t\treturn\n\t}\n\n\tauthHeader, ok := r.Header[\"Authorization\"]\n\tif !ok {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusUnauthorized,\n\t\t\t\"request did not provide Authorization header\")\n\t\treturn\n\t}\n\n\tif len(authHeader) != 1 {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(\"%q auth headers provided. Only 1 allowed\", len(authHeader)))\n\t\treturn\n\t}\n\n\t// Be slightly permissive here. Allow both forms\n\t// Authorization: Bearer ...\n\t// Authorization: ...\n\tjwtString := strings.TrimPrefix(authHeader[0], \"Bearer \")\n\n\tif _, err := h.tv.ValidateJWT(r.Context(), jwtString); err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusForbidden, \"JWT not valid\", ilog.Err(err))\n\t\treturn\n\t}\n}\n\n// Handle requests to verify if a given token has cloud access.\n//\n// The token is verified by testing if the token has `iam.serviceAccounts.actAs`\n// authorization on either the `humanacl` or `robot-service` account by\n// calling the GCP testIamPermissions API. This permission can be granted through e.g. the\n// Service Account User (roles/iam.serviceAccountUser) role.\n//\n// Method: GET\n// URL Parameters:\n// - robots (optional): \"true\" to verify against `robot-service` role, else `humanacl`\n// - token (optional): access token, if not given via header\n// Headers (optional): X_FORWARDED_ACCESS_TOKEN or AUTHORIZATION\n// See function `tokenFromRequest` for details on how to supply the token.\nfunc (h *HandlerContext) verifyTokenHandler(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodGet {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest,\n\t\t\tfmt.Sprintf(\"method %s not allowed, only %s\", r.Method, http.MethodGet))\n\t\treturn\n\t}\n\trobots := testForRobotACL(r.URL)\n\ttoken, err := tokenFromRequest(r.URL, &r.Header)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\terr = h.tv.VerifyToken(r.Context(), oauth.Token(token), robots)\n\tif err != nil {\n\t\tapi.ErrResponse(r.Context(), w, http.StatusForbidden, \"unable to verify token\", ilog.Err(err))\n\t\treturn\n\t}\n\tw.Write([]byte(\"OK\"))\n}\n\n// testForRobotACL determines if the \"robots\" parameter is set.\nfunc testForRobotACL(u *url.URL) bool {\n\trobots, err := getQueryParam(u, \"robots\")\n\tif err != nil || robots != \"true\" {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// tokenFromRequest extracts the access token from the request.\n//\n// The access token can be supplied in one of the following ways, checked in the\n// give order. This is based on the specification of the original java token vendor.\n// 1. Header `X-Forwarded-Access-Token`: Token without prefix\n// 2. Header `Authorization`: Token with \"Bearer \" prefix\n// 3. URL Parameter `token`\nfunc tokenFromRequest(u *url.URL, h *http.Header) (string, error) {\n\tconst fwdToken = \"X-Forwarded-Access-Token\"\n\tif t := h.Get(fwdToken); t != \"\" {\n\t\tif _, err := isValidToken(t); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn t, nil\n\t}\n\tconst authHeader, authPrefix = \"Authorization\", \"Bearer \"\n\tif t := h.Get(authHeader); t != \"\" {\n\t\tif !strings.HasPrefix(t, authPrefix) {\n\t\t\treturn \"\", fmt.Errorf(\"token in header %q has no prefix %q\",\n\t\t\t\tauthHeader, authPrefix)\n\t\t}\n\t\tt = strings.TrimPrefix(t, authPrefix)\n\t\tif _, err := isValidToken(t); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn t, nil\n\t}\n\tconst paramToken = \"token\"\n\tt, err := getQueryParam(u, paramToken)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"no token in headers %q or %q and unable to get token from URL param %q: %v\",\n\t\t\tfwdToken, authHeader, paramToken, err)\n\t}\n\t_, err = isValidToken(t)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn t, nil\n}\n\nconst tokenRegex = `^ya29\\.[a-zA-Z0-9\\.\\-_]+$`\n\nvar tokenMatch = regexp.MustCompile(tokenRegex).MatchString\n\n// isValidToken verifies the format of the token string.\n//\n// The returned error provides details on why the validation failed.\nfunc isValidToken(token string) (bool, error) {\n\t// Minimum length is dummy, maximum from documentation\n\t// Source: https://cloud.google.com/iam/docs/reference/sts/rest/v1/TopLevel/token#response-body\n\tconst minSize, maxSize = 100, 12288\n\tif len(token) < minSize || len(token) > maxSize {\n\t\treturn false, fmt.Errorf(\"invalid token size, assert %d <= %d <= %d\",\n\t\t\tminSize, len(token), maxSize)\n\t}\n\tif !tokenMatch(token) {\n\t\treturn false, fmt.Errorf(\"token failed validation against %q\", tokenRegex)\n\t}\n\treturn true, nil\n}\n\nconst jwtRegex = `^[a-zA-Z0-9\\.\\-_]+\\.[a-zA-Z0-9\\.\\-_]+\\.[a-zA-Z0-9\\.\\-_]+$`\n\nvar jwtMatch = regexp.MustCompile(jwtRegex).MatchString\n\n// isValidJWT verifies the format of an encoded JWT.\n//\n// The returned error provides details on why the validation failed.\nfunc isValidJWT(jwt string) (bool, error) {\n\tconst minSize, maxSize = 100, 5000 // guess\n\tif len(jwt) < minSize || len(jwt) > maxSize {\n\t\treturn false, fmt.Errorf(\"invalid size, assert %d <= %d <= %d\",\n\t\t\tminSize, len(jwt), maxSize)\n\t}\n\tif !jwtMatch(jwt) {\n\t\treturn false, fmt.Errorf(\"jwt failed validation against %q\", jwtRegex)\n\t}\n\treturn true, nil\n}\n\n// Register the API V1 API handler functions to the default http.DefaultServeMux\nfunc Register(tv *app.TokenVendor, prefix string) error {\n\n\tslog.Debug(\"mounting API V1\", slog.String(\"Prefix\", prefix))\n\n\th := NewHandlerContext(tv)\n\n\thttp.HandleFunc(path.Join(prefix, \"public-key.configure\"), h.publicKeyConfigureHandler)\n\thttp.HandleFunc(path.Join(prefix, \"public-key.read\"), h.publicKeyReadHandler)\n\thttp.HandleFunc(path.Join(prefix, \"public-key.publish\"), h.publicKeyPublishHandler)\n\thttp.HandleFunc(path.Join(prefix, \"token.oauth2\"), h.tokenOAuth2Handler)\n\thttp.HandleFunc(path.Join(prefix, \"token.verify\"), h.verifyTokenHandler)\n\thttp.HandleFunc(path.Join(prefix, \"jwt.verify\"), h.verifyJWTHandler)\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/api/v1/v1_test.go",
    "content": "package v1\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/app\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository/k8s\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/tokensource\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nconst (\n\ttestPubKey    = \"testdata/rsa_cert.pem\"\n\tjwtBodyPrefix = \"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=\"\n\tsaName        = \"robot-service@testproject.iam.gserviceaccount.com\"\n\n\tjwtCorrect  = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjoxOTEzMzczMDEwLCJzY29wZXMiOiJ0ZXN0c2NvcGVzIiwiY2xhaW1zIjoidGVzdGNsYWltcyJ9.WJP0shiqynW9ZrmV4k78W3_nn_YA86XLK58IJYyqUF-8LAG92MraNqVqD0t6i-s90VBL64hCXlsA7zP3WlsMHOEvXCyRkGffhbJNIlJqIVTVfGvyF-ZmuaAr352n5kmKTrfTRi7h9LWTcvDgSosN438J8Jy9BT1FE9P-BHfyBUegZ15DWFAiAhz0r_Fgj7hAMXUnRdZfj3_dE0Nhi5IGs3L-0XzU-dE150ZJvtGMdIjc_QCqYHV3wtSgETKDYQoonD08n6g5GqC8nNkqrWFMttafLdPaDAsr8KWtj1dD1w9sw1YJClEzF9JOc63WNPZf8CgdU2enFW-V-2vHbUaekg\"\n\tjwtWrongSig = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjoxOTEzMzczMDEwLCJzY29wZXMiOiIuLi4iLCJjbGFpbXMiOiIuLi4ifQ.krAYHjkConzVudfXJUMiDNbVHF3RwkvOAhSCyTvOaJdlJ6sxh-TjPXo6W0yVT31qjLwhl1NYI-JlhcHX7TLiZbLCbGVXlQN2Nn4LvpbGdAH0KvSJkthqX7ld9tlVQGdlOUHCE5bBDG_9uBtpdOAv1zKUTquhyDM0qWVrQV1qUVOtwBCO6nt21l1eXgTwz50FVN33f1ZmhZfHW1u7Dq_XwBJmHFwN3aiD0NZohU7MpQiz-0u94Q9yZ588IjdZEUhSEUKrVtJjoPcxDhrXxoRMA8iP8_bMeOHteiAdYeBVBwFhu1d8pfcn6uoZROYD1xB1LWDTJx4GfQh6v3wtAwFu7Q\"\n\tjwtWrongAud = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhYmMiLCJpc3MiOiJyb2JvdC1kZXYtdGVzdHVzZXIiLCJleHAiOjE5MTMzNzMwMTAsInNjb3BlcyI6Ii4uLiIsImNsYWltcyI6Ii4uLiJ9.XIoSfJl7QE51XUt7XHvZTomuXAAjVKWhnBhCgZl91-dGO9aF_pVu9sc_kR-MODoZci9pUKaLfqLTbZkNgkwGvApXF4GZ1DBu0uG6ewbNzIA-2l67xztnGw_M5DrQpLnq31HT1hRlvB9cXOYj2qtVfQaOhZtSPeHviYXj1NiPzHIWdyZKGIYu-gofkAZACEKKDd8HBRv6bLOzgrJ9sxlsyIB_O-FzpgoGSH-bKj9QEbSazx1j7AdICq1pJ_ER9ovb0qcYqg1JPToeEB1L-GFGwZp2JAnVp2rbbwPfjQTVlGmmAu-NUA5SjbjrNSjwDnQZDBBhmx75uToptJsnC_xZAw\"\n\tjwtExpired  = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjowLCJzY29wZXMiOiJ0ZXN0c2NvcGVzIiwiY2xhaW1zIjoidGVzdGNsYWltcyJ9.VgalRggp3RrwarTeNSZu-lYcjSOyH7S7g_6BIxV_RisRavbwr1liTUXEKA1fjx5zF_1I_dsPC64wXkK14lAmJFI4pMm8-oLXMgSyUOGAqicYGrRm-CeZ_xJmA37ZCKyyf7ijGCdaAqNbtnsSER3wHTIG7ccbpcEUvb57nCQnTBzlEAVqDFXh9D-7Md2SUmWXCvmWomkALnPPg1xeeWjQygQvmbvFOo37ZgD-GbuvEYr2ccc1otJVvGtpSdJmFc1fGOWy9ZkPMR9VZyrmapYlImZTX7yOfcP-TLcbKRQegt3JJKgvffRP1dhxZaB5fTwT5o5ZTTh7aLap-MUUoZN4bg\"\n)\n\ntype RoundTripFunc func(req *http.Request) *http.Response\n\nfunc (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn f(req), nil\n}\n\nfunc NewTestHTTPClient(fn RoundTripFunc) *http.Client {\n\treturn &http.Client{Transport: fn}\n}\n\ntype publicKeyConfigureHandlerK8sTest struct {\n\tdesc           string\n\tconfigmaps     []*corev1.ConfigMap\n\tmethod         string\n\tdeviceID       string\n\tbody           string\n\twantStatusCode int\n}\n\nfunc TestPublicKeyConfigureHandlerWithK8s(t *testing.T) {\n\tvar cases = []publicKeyConfigureHandlerK8sTest{\n\t\t{\n\t\t\tdesc: \"happy case\",\n\t\t\tconfigmaps: []*corev1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"testdevice\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string]string{\"pubKey\": \"testkey\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tmethod:         http.MethodPost,\n\t\t\tdeviceID:       \"testdevice\",\n\t\t\tbody:           \"{}\",\n\t\t\twantStatusCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tdesc:           \"wrong method, bad request\",\n\t\t\tmethod:         http.MethodGet,\n\t\t\tdeviceID:       \"testdevice\",\n\t\t\twantStatusCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\tdesc:           \"missing device-id, bad request\",\n\t\t\tmethod:         http.MethodPost,\n\t\t\tbody:           \"{}\",\n\t\t\twantStatusCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\tdesc:           \"wrong device-id, not found\",\n\t\t\tmethod:         http.MethodPost,\n\t\t\tdeviceID:       \"testdevice\",\n\t\t\tbody:           \"{}\",\n\t\t\twantStatusCode: http.StatusNotFound,\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\trunPublicKeyConfigureHandlerWithK8sCase(t, &test)\n\t\t})\n\t}\n}\n\nfunc runPublicKeyConfigureHandlerWithK8sCase(t *testing.T, test *publicKeyConfigureHandlerK8sTest) {\n\t// Setup fake K8s environment\n\tcs := fake.NewSimpleClientset()\n\tif err := populateK8sEnv(cs, \"default\", test.configmaps); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tkcl, err := k8s.NewK8sRepository(context.TODO(), cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Setup app & API handler\n\ttv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, \"aud\", saName)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th := HandlerContext{tv: tv}\n\t// Make API call\n\trr := httptest.NewRecorder()\n\treq := mustNewRequest(t, test.method, \"/anything\", strings.NewReader(test.body))\n\tq := req.URL.Query()\n\tq.Add(\"device-id\", test.deviceID)\n\treq.URL.RawQuery = q.Encode()\n\th.publicKeyConfigureHandler(rr, req)\n\t// check API response\n\tif rr.Code != test.wantStatusCode {\n\t\tt.Errorf(\"wrong status code, is %d, want %d\", rr.Code, test.wantStatusCode)\n\t}\n\tif rr.Code != http.StatusOK {\n\t\treturn\n\t}\n}\n\ntype publicKeyReadHandlerK8sTest struct {\n\tdesc           string\n\tconfigmaps     []*corev1.ConfigMap\n\tdeviceID       string\n\twantKey        string\n\twantStatusCode int\n}\n\nfunc TestPublicKeyReadHandlerWithK8s(t *testing.T) {\n\tvar cases = []publicKeyReadHandlerK8sTest{\n\t\t{\n\t\t\t\"key_found\",\n\t\t\t[]*corev1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"testdevice\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string]string{\"pubKey\": \"testkey\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"testdevice\",\n\t\t\t\"testkey\",\n\t\t\thttp.StatusOK,\n\t\t},\n\t\t// key not found is not an error and returns an empty response\n\t\t{\n\t\t\t\"key_not_found\",\n\t\t\t[]*corev1.ConfigMap{},\n\t\t\t\"testdevice\",\n\t\t\t\"\",\n\t\t\thttp.StatusNotFound,\n\t\t},\n\t\t// for malformed configmaps we expect an error\n\t\t{\n\t\t\t\"malformed_configmap\",\n\t\t\t[]*corev1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"testdevice\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// missing Data field\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"testdevice\",\n\t\t\t\"\",\n\t\t\thttp.StatusInternalServerError,\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\trunPublicKeyReadHandlerWithK8sCase(t, &test)\n\t\t})\n\t}\n}\n\nfunc populateK8sEnv(env kubernetes.Interface, ns string, maps []*corev1.ConfigMap) error {\n\tfor _, m := range maps {\n\t\tif _, err := env.CoreV1().ConfigMaps(ns).Create(context.TODO(), m, metav1.CreateOptions{}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc runPublicKeyReadHandlerWithK8sCase(t *testing.T, test *publicKeyReadHandlerK8sTest) {\n\t// Setup fake K8s environment\n\tcs := fake.NewSimpleClientset()\n\tif err := populateK8sEnv(cs, \"default\", test.configmaps); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tkcl, err := k8s.NewK8sRepository(context.TODO(), cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Setup app & API handler\n\ttv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, \"aud\", saName)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th := HandlerContext{tv: tv}\n\t// Make API call\n\trr := httptest.NewRecorder()\n\treq := mustNewRequest(t, \"GET\", \"/anything\", nil)\n\tq := req.URL.Query()\n\tq.Add(\"device-id\", test.deviceID)\n\treq.URL.RawQuery = q.Encode()\n\th.publicKeyReadHandler(rr, req)\n\t// check API response\n\tif rr.Code != test.wantStatusCode {\n\t\tt.Errorf(\"wrong status code, is %d, want %d\", rr.Code, test.wantStatusCode)\n\t}\n\tif rr.Code != http.StatusOK {\n\t\treturn\n\t}\n\tbody, err := io.ReadAll(rr.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgotKey := string(body)\n\tif gotKey != test.wantKey {\n\t\tt.Errorf(\"readHandler(..) = %v, want %v\", gotKey, test.wantKey)\n\t}\n}\n\nfunc mustRespBodyFromFile(t *testing.T, file string) io.ReadCloser {\n\tisResponseBody, err := os.ReadFile(file)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\treturn io.NopCloser(bytes.NewBuffer(isResponseBody))\n}\n\nfunc mustNewRequest(t *testing.T, method, url string, body io.Reader) *http.Request {\n\tr, err := http.NewRequest(method, url, body)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\treturn r\n}\n\nfunc mustFileOpen(t *testing.T, name string) io.Reader {\n\tf, err := os.Open(name)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\treturn f\n}\n\nfunc mustFileToString(t *testing.T, name string) string {\n\tfp := mustFileOpen(t, name)\n\tbytes, err := io.ReadAll(fp)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\treturn string(bytes)\n}\n\ntype publicKeyPublishHandlerK8sTest struct {\n\tdesc       string\n\tconfigmaps []*corev1.ConfigMap\n\tdeviceID   string\n\t// read key before publish\n\twantKeyBefore string\n\t// publish and read key again\n\tbody    io.Reader\n\twantKey string\n}\n\nfunc TestPublicKeyPublishHandlerWithK8s(t *testing.T) {\n\tvar cases = []publicKeyPublishHandlerK8sTest{\n\t\t{\n\t\t\t// happy path where no device is registered yet\n\t\t\t\"register_new_device\",\n\t\t\t[]*corev1.ConfigMap{},\n\t\t\t\"testdevice\",\n\t\t\t\"\",\n\t\t\tmustFileOpen(t, path.Join(\"testdata\", \"rsa_cert.pem\")),\n\t\t\tmustFileToString(t, path.Join(\"testdata\", \"rsa_cert.pem\")),\n\t\t},\n\t\t{\n\t\t\t// happy path where the device is already registered\n\t\t\t\"update_device_key\",\n\t\t\t[]*corev1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"testdevice\",\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string]string{\"pubKey\": \"testkey\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"testdevice\",\n\t\t\t\"testkey\",\n\t\t\tmustFileOpen(t, path.Join(\"testdata\", \"rsa_cert.pem\")),\n\t\t\tmustFileToString(t, path.Join(\"testdata\", \"rsa_cert.pem\")),\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\trunPublicKeyPublishHandlerWithK8sCase(t, &test)\n\t\t})\n\t}\n}\n\nfunc runPublicKeyPublishHandlerWithK8sCase(t *testing.T, test *publicKeyPublishHandlerK8sTest) {\n\t// Setup fake K8s environment\n\tcs := fake.NewSimpleClientset()\n\tif err := populateK8sEnv(cs, \"default\", test.configmaps); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tkcl, err := k8s.NewK8sRepository(context.TODO(), cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Setup app & API handler\n\ttv, err := app.NewTokenVendor(context.TODO(), kcl, nil, nil, \"aud\", saName)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th := HandlerContext{tv: tv}\n\t// Read the current device key\n\trr := httptest.NewRecorder()\n\treq := mustNewRequest(t, \"GET\", \"/anything\", nil)\n\tq := req.URL.Query()\n\tq.Add(\"device-id\", test.deviceID)\n\treq.URL.RawQuery = q.Encode()\n\th.publicKeyReadHandler(rr, req)\n\tif rr.Code != http.StatusOK && rr.Code != http.StatusNotFound {\n\t\tt.Errorf(\"before update,publicKeyReadHandler(..): wrong status code, got %d, want %d/%d\",\n\t\t\trr.Code, http.StatusOK, http.StatusNotFound)\n\t}\n\tbody, err := io.ReadAll(rr.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif rr.Code == http.StatusOK {\n\t\tgotKey := string(body)\n\t\tif gotKey != test.wantKeyBefore {\n\t\t\tt.Errorf(\"before update,publicKeyReadHandler(..): wrong key, got %v, want %v\",\n\t\t\t\tgotKey, test.wantKey)\n\t\t}\n\t}\n\t// POST a new key\n\trr = httptest.NewRecorder()\n\treq = mustNewRequest(t, \"POST\", \"/anything\", test.body)\n\tq = req.URL.Query()\n\tq.Add(\"device-id\", test.deviceID)\n\treq.URL.RawQuery = q.Encode()\n\th.publicKeyPublishHandler(rr, req)\n\t// check API response\n\tif rr.Code != http.StatusOK {\n\t\tt.Errorf(\"publicKeyPublishHandler(..): wrong status code %d, want %d\", rr.Code, http.StatusOK)\n\t}\n\t// Read key back again\n\trr = httptest.NewRecorder()\n\treq = mustNewRequest(t, \"GET\", \"/anything\", nil)\n\tq = req.URL.Query()\n\tq.Add(\"device-id\", test.deviceID)\n\treq.URL.RawQuery = q.Encode()\n\th.publicKeyReadHandler(rr, req)\n\tif rr.Code != http.StatusOK {\n\t\tt.Errorf(\"after update,publicKeyReadHandler(..): wrong status code, got %d, want %d\",\n\t\t\trr.Code, http.StatusOK)\n\t}\n\tbody, err = io.ReadAll(rr.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgotKey := string(body)\n\tif gotKey != test.wantKey {\n\t\tt.Errorf(\"after update,publicKeyReadHandler(..): wrong key, got %v, want %v\",\n\t\t\tgotKey, test.wantKey)\n\t}\n}\n\ntype isValidPublicKeyTest struct {\n\tdesc    string\n\tpk      []byte\n\tisValid bool\n}\n\nfunc TestIsValidPublicKey(t *testing.T) {\n\tvar cases = []isValidPublicKeyTest{\n\t\t{\"testdata key\", []byte(mustFileToString(t, testPubKey)), true},\n\t\t{\"junk string\", []byte(\"some junk string\"), false},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tgotValid, err := isValidPublicKey(test.pk)\n\t\t\tif test.isValid != gotValid {\n\t\t\t\tt.Errorf(\"isValidPublicKey(..): is %v, got %v\", test.isValid, gotValid)\n\t\t\t}\n\t\t\tif test.isValid && err != nil || !test.isValid && err == nil {\n\t\t\t\tt.Errorf(\"isValidPublicKey(..): is %v, but got error %v\", test.isValid, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype isValidTokenTest struct {\n\tdesc    string\n\ttoken   string\n\tisValid bool\n}\n\nfunc TestIsValidToken(t *testing.T) {\n\tconst p = \"ya29.\"\n\tvar cases = []isValidTokenTest{\n\t\t// valid\n\t\t{\"valid\", p + \"a_bc-D0348\" + strings.Repeat(\"a\", 100), true},\n\t\t//invalid\n\t\t{\"empty\", \"\", false}, {\"too short\", \"abc\", false},\n\t\t{\"wrong prefix\", \"ya244\" + strings.Repeat(\"a\", 100), false},\n\t\t{\"new line\", p + strings.Repeat(\"a\", 100) + \"\\n\" + p + strings.Repeat(\"a\", 100), false},\n\t\t{\"wrong characters\", p + strings.Repeat(\"a\", 100) + \"#!\", false},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tgotValid, gotErr := isValidToken(test.token)\n\t\t\tif gotValid != test.isValid {\n\t\t\t\tt.Errorf(\"isValidToken(%q): is %v, got %v\", test.token, test.isValid, gotValid)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (test.isValid && gotErr != nil) || (!test.isValid && gotErr == nil) {\n\t\t\t\tt.Errorf(\"isValidToken(%q): is %v, but got error %v\",\n\t\t\t\t\ttest.token, test.isValid, gotErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype isValidJWTTest struct {\n\tdesc      string\n\tjwt       string\n\twantValid bool\n}\n\nfunc TestIsValidJWT(t *testing.T) {\n\tvar cases = []isValidJWTTest{\n\t\t// valid\n\t\t{\"valid\", strings.Repeat(\"a\", 100) + \".\" + strings.Repeat(\"a\", 100) +\n\t\t\t\".\" + strings.Repeat(\"a\", 100), true},\n\t\t//invalid\n\t\t{\"empty\", \"\", false}, {\"too short\", \"abc\", false}, {\"no dots\", strings.Repeat(\"a\", 100), false},\n\t\t{\"new line\", strings.Repeat(\"a\", 100) + \"\\n\" + strings.Repeat(\"a\", 100), false},\n\t\t{\"wrong characters\", strings.Repeat(\"a\", 100) + \"#!\", false},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tgotValid, gotErr := isValidJWT(test.jwt)\n\t\t\tif gotValid != test.wantValid {\n\t\t\t\tt.Errorf(\"isValidJWT(%q): got %v, want %v, error %v\",\n\t\t\t\t\ttest.jwt, gotValid, test.wantValid, gotErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (test.wantValid && gotErr != nil) || (!test.wantValid && gotErr == nil) {\n\t\t\t\tt.Errorf(\"isValidJWT(%q): got error %v, want %v\",\n\t\t\t\t\ttest.jwt, gotErr, test.wantValid)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype tokenFromRequestTest struct {\n\tdesc    string\n\th       http.Header\n\tu       *url.URL\n\tisToken string\n\tisErr   bool\n}\n\nfunc TestTokenFromRequest(t *testing.T) {\n\tvalidToken := `ya29.a_bc-d` + strings.Repeat(\"a\", 100)\n\tvalidUrl, _ := url.Parse(\"http://127.0.0.1:80/?token=\" + validToken)\n\tvar cases = []tokenFromRequestTest{\n\t\t{\n\t\t\t\"token in auth header\",\n\t\t\thttp.Header{\"Authorization\": {\"Bearer \" + validToken}},\n\t\t\t&url.URL{},\n\t\t\tvalidToken,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"invalid auth header\",\n\t\t\thttp.Header{\"Authorization\": {\"SomethingElse: \" + validToken}},\n\t\t\t&url.URL{},\n\t\t\t\"\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"token in x forwarded header\",\n\t\t\thttp.Header{\"X-Forwarded-Access-Token\": {validToken}},\n\t\t\t&url.URL{},\n\t\t\tvalidToken,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"invalid token in x forwarded header\",\n\t\t\thttp.Header{\"X-Forwarded-Access-Token\": {\"InvalidTokenString\"}},\n\t\t\t&url.URL{},\n\t\t\tvalidToken,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"token in URL\",\n\t\t\thttp.Header{},\n\t\t\tvalidUrl,\n\t\t\tvalidToken,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"no token\",\n\t\t\thttp.Header{},\n\t\t\t&url.URL{},\n\t\t\t\"\",\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tgotToken, gotErr := tokenFromRequest(test.u, &test.h)\n\t\t\tif test.isErr && gotErr == nil || !test.isErr && gotErr != nil {\n\t\t\t\tt.Errorf(\"tokenFromRequest(..): error is %v, but got error %v\", test.isErr, gotErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotErr != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotToken != test.isToken {\n\t\t\t\tt.Errorf(\"tokenFromRequest(..): is %q, got %q\", test.isToken, gotToken)\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype VerifyTokenHandlerTest struct {\n\tdesc string\n\t// handler request variables (test -> handler)\n\treqIsRobots bool\n\t// IAM request variables (token verifier -> GCP IAM)\n\tiamReqIsUrl string\n\t// fake IAM response variables (GCP IAM -> token verifier)\n\tiamRespPermissions string\n\tiamRespStatusCode  int\n\t// handler response variables (handler -> test)\n\thandlerIsStatusCode int\n}\n\nfunc TestVerifyTokenHandler(t *testing.T) {\n\tconst permHappy = `{\"permissions\":[\"iam.serviceAccounts.actAs\"]}`\n\tconst permBad = `{\"permissions\":[]}`\n\tconst isUrlRobotsACL = \"https://iam.googleapis.com/v1/projects/testproject/serviceAccounts/robot-service@testproject.iam.gserviceaccount.com:testIamPermissions?alt=json&prettyPrint=false\"\n\tconst isUrlHumanACL = \"https://iam.googleapis.com/v1/projects/testproject/serviceAccounts/human-acl@testproject.iam.gserviceaccount.com:testIamPermissions?alt=json&prettyPrint=false\"\n\n\tvar cases = []VerifyTokenHandlerTest{\n\t\t{\"human-acl happy path\", false, isUrlHumanACL, permHappy, http.StatusOK, http.StatusOK},\n\t\t{\"human-acl missing permission\", false, isUrlHumanACL, permBad, http.StatusOK, http.StatusForbidden},\n\t\t{\"robots-service happy path\", true, isUrlRobotsACL, permHappy, http.StatusOK, http.StatusOK},\n\t\t{\"error from GCP IAM\", true, isUrlRobotsACL, permHappy, http.StatusBadRequest, http.StatusForbidden},\n\t}\n\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\trunVerifyTokenHandlerTest(t, &test)\n\t\t})\n\t}\n}\n\nfunc runVerifyTokenHandlerTest(t *testing.T, test *VerifyTokenHandlerTest) {\n\tisToken := \"ya29.\" + strings.Repeat(\"a\", 100)\n\t// fake IAM response\n\tfakeIAMHandler := func(req *http.Request) *http.Response {\n\t\tif req.Method != http.MethodPost {\n\t\t\tt.Fatalf(\"unexpected request method %q\", req.Method)\n\t\t}\n\t\tif req.URL.String() != test.iamReqIsUrl {\n\t\t\tt.Fatalf(\"wrong POST URL, is %q, got %q\", test.iamReqIsUrl, req.URL)\n\t\t}\n\t\tauth := req.Header.Get(\"Authorization\")\n\t\tif !strings.HasPrefix(auth, \"Bearer \") {\n\t\t\tt.Fatal(\"missing auth bearer prefix\")\n\t\t}\n\t\tgotToken := strings.TrimPrefix(auth, \"Bearer \")\n\t\tif isToken != gotToken {\n\t\t\tt.Fatalf(\"wrong token, is %q, got %q\", isToken, gotToken)\n\t\t}\n\t\t// only check if the permission is in the request body and not unmarshal the whole json\n\t\tbody, err := io.ReadAll(req.Body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err.Error())\n\t\t}\n\t\tif !strings.Contains(string(body), \"iam.serviceAccounts.actAs\") {\n\t\t\tt.Fatalf(\"request does not contain expected permission\")\n\t\t}\n\t\t// respond with the requested permissions\n\t\treturn &http.Response{\n\t\t\tStatusCode: test.iamRespStatusCode,\n\t\t\tBody:       io.NopCloser(strings.NewReader(test.iamRespPermissions)),\n\t\t\tHeader:     make(http.Header),\n\t\t}\n\t}\n\n\t// setup app and http client\n\tclient := NewTestHTTPClient(fakeIAMHandler)\n\ttver, err := oauth.NewTokenVerifier(context.TODO(), client, \"testproject\")\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\ttv, err := app.NewTokenVendor(context.TODO(), nil, tver, nil, \"aud\", saName)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\th := &HandlerContext{tv: tv}\n\n\t// make request to the handler\n\trr := httptest.NewRecorder()\n\treq := mustNewRequest(t, \"GET\", \"/anything\", nil)\n\treq.Header.Add(\"Authorization\", \"Bearer \"+isToken)\n\tq := req.URL.Query()\n\tq.Add(\"robots\", strconv.FormatBool(test.reqIsRobots))\n\treq.URL.RawQuery = q.Encode()\n\th.verifyTokenHandler(rr, req)\n\n\t// check response\n\tif rr.Code != test.handlerIsStatusCode {\n\t\tt.Errorf(\"wrong status code, is %d, got %d\", test.handlerIsStatusCode, rr.Code)\n\t}\n}\n\ntype TokenOAuth2HandlerTest struct {\n\tdesc string\n\t// token vendor config\n\tacceptedAud string\n\tscopes      []string\n\t// test -> handler\n\tbody string\n\t// fake GCP -> Token Vendor\n\ttoken  string\n\texpire string\n\t// handler -> test\n\twantStatusCode int\n}\n\n// we create a happy test first and afterwards variations of it\nvar TokenOAuth2HandlerTestHappyPath = TokenOAuth2HandlerTest{\n\tdesc:        \"happy path\",\n\tacceptedAud: \"testaud\",\n\tscopes:      []string{\"abc\", \"def\"},\n\t// token defined in oauth/jwt/jwt_test.go\n\tbody:  jwtBodyPrefix + jwtCorrect,\n\ttoken: \"abc\",\n\t// expire needs to be the same across all tests because checked the same across all tests\n\texpire:         \"2100-06-30T15:01:23.045123456Z\",\n\twantStatusCode: 200,\n}\n\nfunc TestTokenOAuth2HandlerHapyPath(t *testing.T) {\n\tt.Run(\"with_k8s\", func(t *testing.T) {\n\t\trunTokenOAuth2HandlerTestWithK8s(t, TokenOAuth2HandlerTestHappyPath)\n\t})\n}\n\nfunc TestTokenOAuth2HandlerDifferentPrivateKey(t *testing.T) {\n\ttest := TokenOAuth2HandlerTestHappyPath\n\ttest.desc = \"JWT signed with different private key\"\n\t// JWT is valid but signed with a different (random) private key not matching\n\t// the one returned from the registry for the given device\n\ttest.body = jwtBodyPrefix + jwtWrongSig\n\ttest.wantStatusCode = 403\n\tt.Run(\"with_k8s\", func(t *testing.T) {\n\t\trunTokenOAuth2HandlerTestWithK8s(t, test)\n\t})\n}\n\nfunc TestTokenOAuth2HandlerWrongAud(t *testing.T) {\n\ttest := TokenOAuth2HandlerTestHappyPath\n\ttest.desc = \"invalid JWT (junk audience)\"\n\t// JWT \"aud\" is changed to \"abc\"\n\ttest.body = jwtBodyPrefix + jwtWrongAud\n\ttest.wantStatusCode = 403\n\tt.Run(\"with_k8s\", func(t *testing.T) {\n\t\trunTokenOAuth2HandlerTestWithK8s(t, test)\n\t})\n}\n\nfunc TestTokenOAuth2HandlerExpired(t *testing.T) {\n\ttest := TokenOAuth2HandlerTestHappyPath\n\ttest.desc = \"JWT signed correctly but expired\"\n\t// JWT is valid but signed with a different (random) private key not matching\n\t// the one returned from the registry for the given device\n\ttest.body = jwtBodyPrefix + jwtExpired\n\ttest.wantStatusCode = 403\n\tt.Run(\"with_k8s\", func(t *testing.T) {\n\t\trunTokenOAuth2HandlerTestWithK8s(t, test)\n\t})\n}\n\nfunc runTokenOAuth2HandlerTestWithK8s(t *testing.T, test TokenOAuth2HandlerTest) {\n\t// fake GCP IAM response for an access token\n\tfakeIAMAPI := func(req *http.Request) *http.Response {\n\t\tconst wantUrl = \"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/robot-service@testproject.iam.gserviceaccount.com:generateAccessToken?alt=json&prettyPrint=false\"\n\t\tif req.URL.String() != wantUrl {\n\t\t\tt.Fatalf(\"wrong IAM URL, got %q, want %q\", req.URL, wantUrl)\n\t\t}\n\t\tbody := `{\n\t\t\t\"accessToken\": \"` + test.token + `\",\n\t\t\t\"expireTime\": \"` + test.expire + `\"\n\t\t  }`\n\t\treturn &http.Response{\n\t\t\tStatusCode: http.StatusOK,\n\t\t\tBody:       io.NopCloser(strings.NewReader(body)),\n\t\t\tHeader:     make(http.Header),\n\t\t}\n\t}\n\t// setup app and http client\n\tclientIAM := NewTestHTTPClient(fakeIAMAPI)\n\tts, err := tokensource.NewGCPTokenSource(context.TODO(), clientIAM, test.scopes)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcs := fake.NewSimpleClientset()\n\tif err = populateK8sEnv(cs, \"default\",\n\t\t[]*corev1.ConfigMap{\n\t\t\t{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"robot-dev-testuser\",\n\t\t\t\t},\n\t\t\t\tData: map[string]string{\"pubKey\": \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQAB\\n-----END PUBLIC KEY-----\"},\n\t\t\t},\n\t\t}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tr, err := k8s.NewK8sRepository(context.TODO(), cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttv, err := app.NewTokenVendor(context.TODO(), r, nil, ts, test.acceptedAud, saName)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th := &HandlerContext{tv: tv}\n\n\t// make request to the handler\n\trr := httptest.NewRecorder()\n\treq := mustNewRequest(t, \"POST\", \"/anything\", io.NopCloser(strings.NewReader(test.body)))\n\th.tokenOAuth2Handler(rr, req)\n\n\t// check response\n\tif rr.Code != test.wantStatusCode {\n\t\tt.Fatalf(\"wrong status code, got %d, want %d\", rr.Code, test.wantStatusCode)\n\t}\n\t// no body is provided in case of bad requests\n\tif test.wantStatusCode != 200 {\n\t\treturn\n\t}\n\tvar resp tokensource.TokenResponse\n\terr = json.Unmarshal(rr.Body.Bytes(), &resp)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to unmarshal response body, got body %q\", rr.Body)\n\t}\n\tif resp.AccessToken != test.token {\n\t\tt.Fatalf(\"Token(..) access token: got %q, want %q\", resp.AccessToken, test.token)\n\t}\n\twantScopes := strings.Join(test.scopes, \" \")\n\tif resp.Scope != wantScopes {\n\t\tt.Fatalf(\"Token(..) scopes: got %v, want %v\", resp.Scope, wantScopes)\n\t}\n\tif resp.TokenType != \"Bearer\" {\n\t\tt.Fatalf(\"Token(..) token type: got %q, want %q\", resp.TokenType, \"Bearer\")\n\t}\n\t// test will fail in the year 2070\n\tif resp.ExpiresIn < 1_507_248_000 || resp.ExpiresIn > 2_453_852_873 {\n\t\tt.Fatalf(\"Token(..) expires in wrong, got %d, wanted far in the future, but not too far\",\n\t\t\tresp.ExpiresIn)\n\t}\n}\n\nfunc Test_verifyJWTHandler(t *testing.T) {\n\ttestCases := []struct {\n\t\tname         string\n\t\theaders      map[string][]string\n\t\texpectedCode int\n\t}{\n\t\t{\n\t\t\tname:         \"success\",\n\t\t\theaders:      map[string][]string{\"Authorization\": []string{jwtCorrect}},\n\t\t\texpectedCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname:         \"no-auth-header\",\n\t\t\texpectedCode: http.StatusUnauthorized,\n\t\t},\n\t\t{\n\t\t\tname:         \"wrong-sig\",\n\t\t\theaders:      map[string][]string{\"Authorization\": []string{jwtWrongSig}},\n\t\t\texpectedCode: http.StatusForbidden,\n\t\t},\n\t\t{\n\t\t\tname:         \"wrong-aud\",\n\t\t\theaders:      map[string][]string{\"Authorization\": []string{jwtWrongAud}},\n\t\t\texpectedCode: http.StatusForbidden,\n\t\t},\n\t\t{\n\t\t\tname:         \"expired\",\n\t\t\theaders:      map[string][]string{\"Authorization\": []string{jwtExpired}},\n\t\t\texpectedCode: http.StatusForbidden,\n\t\t},\n\t}\n\tt.Parallel()\n\n\tcs := fake.NewSimpleClientset()\n\tif err := populateK8sEnv(cs, \"default\",\n\t\t[]*corev1.ConfigMap{\n\t\t\t{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tKind:       \"ConfigMap\",\n\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"robot-dev-testuser\",\n\t\t\t\t},\n\t\t\t\tData: map[string]string{\"pubKey\": \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQAB\\n-----END PUBLIC KEY-----\"},\n\t\t\t},\n\t\t}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tr, err := k8s.NewK8sRepository(context.TODO(), cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttv, err := app.NewTokenVendor(context.TODO(), r, nil, nil, \"testaud\", saName)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\th := &HandlerContext{tv: tv}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\treq := mustNewRequest(t, http.MethodGet, \"\", nil)\n\t\t\treq.Header = tc.headers\n\n\t\t\th.verifyJWTHandler(rr, req)\n\n\t\t\tif rr.Result().StatusCode != tc.expectedCode {\n\t\t\t\tt.Errorf(\"verifyJWTHandler wrong status. Expected %v got %v\", tc.expectedCode, rr.Result().StatusCode)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/app/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"tokenvendor.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/app\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/oauth:go_default_library\",\n        \"//src/go/cmd/token-vendor/oauth/jwt:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n        \"//src/go/cmd/token-vendor/tokensource:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus:go_default_library\",\n        \"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"tokenvendor_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/oauth:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n        \"//src/go/cmd/token-vendor/repository/memory:go_default_library\",\n        \"//src/go/cmd/token-vendor/tokensource:go_default_library\",\n        \"@com_github_form3tech_oss_jwt_go//:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/app/tokenvendor.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage app\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/mail\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth/jwt\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/tokensource\"\n\t\"github.com/googlecloudrobotics/ilog\"\n)\n\ntype TokenVendor struct {\n\trepo          repository.PubKeyRepository\n\tv             *oauth.TokenVerifier\n\tts            *tokensource.GCPTokenSource\n\taccAud        string\n\tdefaultSAName string\n}\n\nfunc NewTokenVendor(ctx context.Context, repo repository.PubKeyRepository, v *oauth.TokenVerifier, ts *tokensource.GCPTokenSource, acceptedAudience, defaultSAName string) (*TokenVendor, error) {\n\tif acceptedAudience == \"\" {\n\t\treturn nil, errors.New(\"accepted audience must not be empty\")\n\t}\n\treturn &TokenVendor{repo: repo, v: v, ts: ts, accAud: acceptedAudience, defaultSAName: defaultSAName}, nil\n}\n\nfunc (tv *TokenVendor) PublishPublicKey(ctx context.Context, deviceID, publicKey string) error {\n\tslog.Info(\"Publishing public Key\", slog.String(\"DeviceID\", deviceID))\n\treturn tv.repo.PublishKey(ctx, deviceID, publicKey)\n}\n\nfunc (tv *TokenVendor) ReadPublicKey(ctx context.Context, deviceID string) (string, error) {\n\tslog.Debug(\"Returning public Key\", slog.String(\"DeviceID\", deviceID))\n\tkey, err := tv.repo.LookupKey(ctx, deviceID)\n\tif key != nil {\n\t\treturn key.PublicKey, nil\n\t}\n\treturn \"\", err\n}\n\nfunc (tv *TokenVendor) ConfigurePublicKey(ctx context.Context, deviceID string, opts repository.KeyOptions) error {\n\tif err := validateKeyOptions(opts); err != nil {\n\t\tslog.Error(\"Configuring public Key\", ilog.Err(err))\n\t\treturn err\n\t}\n\tslog.Debug(\"Configuring public Key\", slog.String(\"DeviceID\", deviceID))\n\treturn tv.repo.ConfigureKey(ctx, deviceID, opts)\n}\n\nfunc validateKeyOptions(opts repository.KeyOptions) error {\n\t// Sanity check for the config values\n\tif opts.ServiceAccount != \"\" {\n\t\tif err := validateEmail(opts.ServiceAccount); err != nil {\n\t\t\treturn fmt.Errorf(\"ServiceAccount field is not a valid email address: %v\", err)\n\t\t}\n\t}\n\tif opts.ServiceAccountDelegate != \"\" {\n\t\tif err := validateEmail(opts.ServiceAccountDelegate); err != nil {\n\t\t\treturn fmt.Errorf(\"ServiceAccountDelegate field is not a valid email address: %v\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateEmail(e string) error {\n\t_, err := mail.ParseAddress(e)\n\treturn err\n}\n\nvar (\n\ttokensRequested = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"tokens_requested\",\n\t\t\tHelp: \"Number of tokens requested\",\n\t\t},\n\t\t[]string{\"result\"},\n\t)\n\ttokensRequestedDurations = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"tokens_requested_durations\",\n\t\t\tHelp: \"Time it took to retrieve a token or fail in ms\",\n\t\t},\n\t\t[]string{\"result\"},\n\t)\n)\n\nfunc (tv *TokenVendor) GetOAuth2Token(ctx context.Context, jwtk string) (*tokensource.TokenResponse, error) {\n\tts := time.Now()\n\tr, err := tv.getOAuth2Token(ctx, jwtk)\n\tvar state string\n\tif err != nil {\n\t\tstate = \"failed\"\n\t} else {\n\t\tstate = \"success\"\n\t}\n\ttokensRequested.WithLabelValues(state).Inc()\n\ttokensRequestedDurations.WithLabelValues(state).Observe(float64(time.Since(ts).Milliseconds()))\n\treturn r, err\n}\n\n// DeviceAuth contains authorization information for device with DeviceID.\n// Information is extracted from request's OAuth2 JWT Payload\ntype DeviceAuth struct {\n\tDeviceID string\n\tKey      *repository.Key\n\n\t// ServiceAcc represents IAM service account (subject) this device auth\n\t// is requesting to impersonate. Empty value indicates request was made\n\t// to base SA associated with this device and no impersonation is requested.\n\tServiceAcc string\n}\n\n// used for testing\nvar jwtVerifySignature = jwt.VerifySignature\n\nfunc (tv *TokenVendor) ValidateJWT(ctx context.Context, jwtk string) (*DeviceAuth, error) {\n\tp, err := jwt.PayloadUnsafe(jwtk)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to extract JWT payload\")\n\t}\n\texp := time.Unix(p.Exp, 0)\n\tif exp.Before(time.Now()) {\n\t\treturn nil, fmt.Errorf(\"JWT has expired %v, %v ago (iss: %q)\",\n\t\t\texp, time.Since(exp), p.Iss)\n\t}\n\tif err := acceptedAudience(p.Aud, tv.accAud); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"validation of JWT audience failed (iss: %q)\", p.Iss)\n\t}\n\tif !IsValidDeviceID(p.Iss) {\n\t\treturn nil, fmt.Errorf(\"missing or invalid device identifier (`iss`: %q)\", p.Iss)\n\t}\n\tdeviceID := p.Iss\n\tk, err := tv.repo.LookupKey(ctx, deviceID)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to retrieve public Key for device %q\", deviceID)\n\t}\n\tif k.PublicKey == \"\" {\n\t\treturn nil, errors.Errorf(\"no public Key found for device %q\", deviceID)\n\t}\n\terr = jwtVerifySignature(jwtk, k.PublicKey)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to verify signature for device %q\", deviceID)\n\t}\n\n\teffectiveSub := p.Sub\n\tif validateEmail(p.Sub) != nil || !strings.HasSuffix(p.Sub, \".iam.gserviceaccount.com\") {\n\t\t// To support legacy systems we are going to ignore subjects,\n\t\t// which cannot represent service accounts.\n\t\teffectiveSub = \"\"\n\t}\n\n\treturn &DeviceAuth{\n\t\tDeviceID:   deviceID,\n\t\tKey:        k,\n\t\tServiceAcc: effectiveSub,\n\t}, nil\n}\n\nfunc (tv *TokenVendor) getOAuth2Token(ctx context.Context, jwtk string) (*tokensource.TokenResponse, error) {\n\tauthInfo, err := tv.ValidateJWT(ctx, jwtk)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsaName := authInfo.Key.SAName\n\tdelegate := \"\"\n\tif saName == \"\" {\n\t\tsaName = tv.defaultSAName\n\t}\n\tif authInfo.ServiceAcc != \"\" && saName != authInfo.ServiceAcc {\n\t\t// Device requested to impersonate a service account for this\n\t\t// key. We are going to check if we recognize this service account.\n\t\t// If no, we reject the request. If yes, we will request a token\n\t\t// impersonating given account.\n\t\t// There are two lines of defense here:\n\t\t// 1. Device cannot request random SA, only one which TV is aware of.\n\t\t// 2. GCP IAM must have given impersonation chain set, in order to return valid token.\n\t\tslog.Info(\"Device requested to impersonate other service account\", slog.String(\"ActAs\", authInfo.ServiceAcc))\n\t\tif authInfo.ServiceAcc != authInfo.Key.SADelegateName {\n\t\t\tslog.Warn(\"Device requested to impersonate unknown service account\", slog.String(\"ActAs\", authInfo.ServiceAcc),\n\t\t\t\tslog.String(\"Allowed\", authInfo.Key.SADelegateName))\n\t\t\treturn nil, fmt.Errorf(\"device %q requested to impersonate unknown delegate %q\", authInfo.DeviceID, authInfo.ServiceAcc)\n\t\t}\n\t\tdelegate = saName\n\t\tsaName = authInfo.ServiceAcc\n\t}\n\tcloudToken, err := tv.ts.Token(ctx, saName, delegate)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to retrieve a cloud token for device %q\", authInfo.DeviceID)\n\t}\n\tslog.Info(\"Handing out cloud token\", slog.String(\"DeviceID\", authInfo.DeviceID), slog.String(\"ServiceAccount\", saName),\n\t\tslog.String(\"ActAs\", authInfo.ServiceAcc))\n\treturn cloudToken, nil\n}\n\n// acceptedAudience validates JWT audience as defined by the token vendor\n//\n// `aud` is the value of the audience Key from the JWT and `accAud` the configured\n// accepted audience of the token vendor.\nfunc acceptedAudience(aud, accAud string) error {\n\tqualified := accAud + \"?token_type=access_token\"\n\tauds := strings.Split(aud, \" \")\n\tif !contains(auds, accAud) && !contains(auds, qualified) {\n\t\treturn fmt.Errorf(\"audience must contain %q or %q\", accAud, qualified)\n\t}\n\treturn nil\n}\n\nfunc contains(s []string, str string) bool {\n\tfor _, v := range s {\n\t\tif v == str {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\ttokensVerified = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"tokens_verified\",\n\t\t\tHelp: \"Number of tokens verified\",\n\t\t},\n\t\t[]string{\"acl\", \"result\"},\n\t)\n\ttokensVerifiedDurations = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"tokens_verified_durations\",\n\t\t\tHelp: \"Time it took to check a token in ms\",\n\t\t},\n\t\t[]string{\"acl\", \"result\"},\n\t)\n)\n\nfunc init() {\n\t// pre-register label values we know\n\tfor _, acl := range []string{\"robot-service\", \"human-acl\"} {\n\t\tfor _, result := range []string{\"success\", \"failed\"} {\n\t\t\ttokensVerified.WithLabelValues(acl, result)\n\t\t\ttokensVerifiedDurations.WithLabelValues(acl, result)\n\t\t}\n\t}\n\tfor _, result := range []string{\"success\", \"failed\"} {\n\t\ttokensRequested.WithLabelValues(result)\n\t\ttokensRequestedDurations.WithLabelValues(result)\n\t}\n}\n\nfunc (tv *TokenVendor) VerifyToken(ctx context.Context, token oauth.Token, robots bool) error {\n\tvar acl string\n\tif robots {\n\t\tacl = \"robot-service\"\n\t} else {\n\t\tacl = \"human-acl\"\n\t}\n\tslog.Debug(\"Verifying token\", slog.String(\"ACL\", acl))\n\tts := time.Now()\n\terr := tv.v.Verify(ctx, token, acl)\n\tvar state string\n\tif err != nil {\n\t\tstate = \"failed\"\n\t} else {\n\t\tstate = \"success\"\n\t}\n\ttokensVerified.WithLabelValues(acl, state).Inc()\n\ttokensVerifiedDurations.WithLabelValues(acl, state).Observe(float64(time.Since(ts).Milliseconds()))\n\treturn err\n}\n\n// Regex for RFC 1123 subdomain format\n// The device identifier is used as name for the Kubernetes configmap and thus is validated\n// using the same regex as it is used by the Kubernetes API.\n// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names\n// https://github.com/kubernetes/kubernetes/blob/976a940f4a4e84fe814583848f97b9aafcdb083f/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go#L209\nvar isValidDeviceIDRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`).MatchString\n\n// IsValidDeviceID validates the given identifier for string length and characters used\n//\n// Validation is based on the RFC952 for hostnames.\nfunc IsValidDeviceID(ID string) bool {\n\tconst minLen, maxLen = 3, 255\n\tl := len(ID)\n\tif l < minLen || l > maxLen {\n\t\treturn false\n\t}\n\tif !isValidDeviceIDRegex(ID) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/app/tokenvendor_test.go",
    "content": "package app\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/form3tech-oss/jwt-go\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository/memory\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/tokensource\"\n)\n\ntype isValidDeviceIDTest struct {\n\tdeviceId string\n\tret      bool\n}\n\nfunc TestValidateDeviceId(t *testing.T) {\n\n\tvar isValidDeviceIDTests = []isValidDeviceIDTest{\n\t\t// invalid\n\t\t{\"\", false}, {`\\\\\\\\\\\\\\\\\\\\\\\\\\`, false}, {\"t\\nt\", false}, {\"1\", false},\n\t\t{\"robot-dev-\\ndevice\", false},\n\t\t{\"AAAAAAAAAAAAAAAAAAAarobot-dev-device-\\neuwest1-test-com\", false},\n\t\t{\"TEST.com\", false}, {\"TEST.com.\", false}, {\"1-.test\", false},\n\t\t// valid\n\t\t{\"robot-dev-device\", true}, {\"1test\", true}, {\"1-test\", true},\n\t}\n\n\tfor _, test := range isValidDeviceIDTests {\n\t\tv := IsValidDeviceID(test.deviceId)\n\t\tif v != test.ret {\n\t\t\tt.Errorf(\"isValidDeviceID(%q), got %v, want %v\", test.deviceId, v, test.ret)\n\t\t}\n\t}\n}\n\ntype acceptedAudienceTest struct {\n\tdesc         string\n\taud          string\n\taccAud       string\n\twantAccepted bool\n}\n\nfunc TestAcceptedAudience(t *testing.T) {\n\tvar cases = []acceptedAudienceTest{\n\t\t// accepted audiences\n\t\t{\"URL exact\", \"http://something/\", \"http://something/\", true},\n\t\t{\"URL one of them\", \"http://anything/ http://something/\", \"http://something/\", true},\n\t\t{\"URL with parameters\", \"http://something/?token_type=access_token\", \"http://something/\", true},\n\t\t// not accepted audiences\n\t\t{\"empty\", \"\", \"http://something/\", false},\n\t\t{\"some other URL\", \"http://something/else\", \"http://something/\", false},\n\t\t{\"URL one of them, but concat\", \"http://anything/http://something/\", \"http://something/\", false},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\terr := acceptedAudience(test.aud, test.accAud)\n\t\t\tif (err == nil && !test.wantAccepted) || (err != nil && test.wantAccepted) {\n\t\t\t\tt.Fatalf(\"acceptedAudience(%q, %q): got err %v, want %v\",\n\t\t\t\t\ttest.aud, test.accAud, err, test.wantAccepted)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype serviceAccountNameTest struct {\n\tdesc      string\n\tcfgSA     string\n\treqSA     string\n\twantSA    string\n\twantError bool\n}\n\ntype keyOptionsTest struct {\n\tdesc      string\n\topts      repository.KeyOptions\n\twantError bool\n}\n\nfunc TestKeyOptions(t *testing.T) {\n\tvar cases = []keyOptionsTest{\n\t\t{\n\t\t\tdesc: \"empty opts are good\",\n\t\t\topts: repository.KeyOptions{},\n\t\t},\n\t\t{\n\t\t\tdesc: \"one good email, one empty\",\n\t\t\topts: repository.KeyOptions{ServiceAccount: \"svc@example.com\"},\n\t\t},\n\t\t{\n\t\t\tdesc: \"two good emails\",\n\t\t\topts: repository.KeyOptions{ServiceAccount: \"svc@example.com\", ServiceAccountDelegate: \"del@example.com\"},\n\t\t},\n\t\t{\n\t\t\tdesc:      \"bad email #1\",\n\t\t\topts:      repository.KeyOptions{ServiceAccount: \"svc\"},\n\t\t\twantError: true,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"bad email #2\",\n\t\t\topts:      repository.KeyOptions{ServiceAccount: \"svc@\"},\n\t\t\twantError: true,\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\terr := validateKeyOptions(test.opts)\n\t\t\thaveError := err != nil\n\t\t\tif haveError != test.wantError {\n\t\t\t\tif test.wantError {\n\t\t\t\t\tt.Fatalf(\"validateKeyOptions returned %v, but wanted error\", err)\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatalf(\"validateKeyOptions returned %v, but wanted no error\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTokenVendor_ValidateJWT(t *testing.T) {\n\ttype fields struct {\n\t\trepo          repository.PubKeyRepository\n\t\tv             *oauth.TokenVerifier\n\t\tts            *tokensource.GCPTokenSource\n\t\taccAud        string\n\t\tdefaultSAName string\n\t}\n\n\tconst deviceId = \"test-device-id\"\n\tdevicePubKey := &repository.Key{\n\t\tPublicKey: \"test-device-public-key\",\n\t}\n\n\tdefaultFields := fields{\n\t\trepo:          getInMemoryRepo(deviceId, devicePubKey.PublicKey),\n\t\tv:             nil,\n\t\tts:            nil,\n\t\taccAud:        \"\",\n\t\tdefaultSAName: \"robot-service@testing.iam.gserviceaccount.com\",\n\t}\n\n\ttype args struct {\n\t\tjwtk string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\targs    args\n\t\twant    *DeviceAuth\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:   \"sa-success\",\n\t\t\tfields: defaultFields,\n\t\t\targs: args{\n\t\t\t\tjwtk: createFakeJWTWithSubject(t, deviceId, createSAFromName(\"robot-service\")),\n\t\t\t},\n\t\t\twant: &DeviceAuth{\n\t\t\t\tDeviceID:   deviceId,\n\t\t\t\tKey:        devicePubKey,\n\t\t\t\tServiceAcc: createSAFromName(\"robot-service\"),\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"jwt-subject-empty\",\n\t\t\tfields: defaultFields,\n\t\t\targs: args{\n\t\t\t\tjwtk: createFakeJWTWithSubject(t, deviceId, \"\"),\n\t\t\t},\n\t\t\twant: &DeviceAuth{\n\t\t\t\tDeviceID:   deviceId,\n\t\t\t\tKey:        devicePubKey,\n\t\t\t\tServiceAcc: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"jwt-subject-not-an-SA\",\n\t\t\tfields: defaultFields,\n\t\t\targs: args{\n\t\t\t\tjwtk: createFakeJWTWithSubject(t, deviceId, \"robot-rock\"),\n\t\t\t},\n\t\t\twant: &DeviceAuth{\n\t\t\t\tDeviceID:   deviceId,\n\t\t\t\tKey:        devicePubKey,\n\t\t\t\tServiceAcc: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"jwt-subject-not-gcp-SA\",\n\t\t\tfields: defaultFields,\n\t\t\targs: args{\n\t\t\t\tjwtk: createFakeJWTWithSubject(t, deviceId, \"robot-rock@iam.gserviceaccount.com\"),\n\t\t\t},\n\t\t\twant: &DeviceAuth{\n\t\t\t\tDeviceID:   deviceId,\n\t\t\t\tKey:        devicePubKey,\n\t\t\t\tServiceAcc: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"jwt-subject-shall-not-pass\",\n\t\t\tfields: defaultFields,\n\t\t\targs: args{\n\t\t\t\tjwtk: createFakeJWTWithSubject(t, deviceId, \"robot-rock@.iam.gserviceaccount.com\"),\n\t\t\t},\n\t\t\twant: &DeviceAuth{\n\t\t\t\tDeviceID:   deviceId,\n\t\t\t\tKey:        devicePubKey,\n\t\t\t\tServiceAcc: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:   \"jwt-subject-not-really-SA\",\n\t\t\tfields: defaultFields,\n\t\t\targs: args{\n\t\t\t\tjwtk: createFakeJWTWithSubject(t, deviceId, \"arn:aws:iam::123456789012:role/my-role\"),\n\t\t\t},\n\t\t\twant: &DeviceAuth{\n\t\t\t\tDeviceID:   deviceId,\n\t\t\t\tKey:        devicePubKey,\n\t\t\t\tServiceAcc: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tjwtVerifySignature = func(jwtk string, pubKey string) error {\n\t\t// Tests do not validate signature\n\t\treturn nil\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttv := &TokenVendor{\n\t\t\t\trepo:          tt.fields.repo,\n\t\t\t\tv:             tt.fields.v,\n\t\t\t\tts:            tt.fields.ts,\n\t\t\t\taccAud:        tt.fields.accAud,\n\t\t\t\tdefaultSAName: tt.fields.defaultSAName,\n\t\t\t}\n\t\t\tgot, err := tv.ValidateJWT(context.Background(), tt.args.jwtk)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ValidateJWT() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"ValidateJWT() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc getInMemoryRepo(deviceId, key string) repository.PubKeyRepository {\n\tctx := context.Background()\n\trepo, _ := memory.NewMemoryRepository(ctx)\n\t_ = repo.PublishKey(ctx, deviceId, key)\n\treturn repo\n}\n\nfunc createFakeJWTWithSubject(t *testing.T, deviceId, subject string) string {\n\tjwtWithSubject := jwt.StandardClaims{\n\t\tAudience:  nil,\n\t\tExpiresAt: time.Now().Add(10 * time.Second).Unix(),\n\t\tId:        t.Name(),\n\t\tIssuedAt:  0,\n\t\tIssuer:    deviceId,\n\t\tNotBefore: 0,\n\t\tSubject:   subject,\n\t}\n\n\tbuffer := new(bytes.Buffer)\n\tif err := json.NewEncoder(buffer).Encode(&jwtWithSubject); err != nil {\n\t\tt.Errorf(\"failed to serialize jwt: %v\", err)\n\t}\n\n\treturn jwt.EncodeSegment([]byte(`{\"typ\": \"JWT\"}`)) +\n\t\t\".\" + jwt.EncodeSegment(buffer.Bytes()) +\n\t\t\".\" + jwt.EncodeSegment([]byte(\"no-signature-present\"))\n}\n\nfunc createSAFromName(name string) string {\n\treturn fmt.Sprintf(\"%s@%s.iam.gserviceaccount.com\", name, \"test-project\")\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/main.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/rest\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/api\"\n\tapiv1 \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/api/v1\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/app\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository/k8s\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository/memory\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/tokensource\"\n\t\"github.com/googlecloudrobotics/ilog\"\n)\n\ntype scopeFlags []string\n\nfunc (i *scopeFlags) String() string {\n\treturn strings.Join(*i, \",\")\n}\n\nfunc (i *scopeFlags) Set(value string) error {\n\t*i = append(*i, value)\n\treturn nil\n}\n\ntype KeyStoreOpt string\n\nconst (\n\tKubernetes = \"KUBERNETES\"\n\tMemory     = \"IN_MEMORY\"\n)\n\n// Supported public key backends.\nvar keyStoreOpts = []string{string(Kubernetes), string(Memory)}\n\nvar (\n\tverbose  = flag.Bool(\"verbose\", false, \"DEPRECTAED: Use log_level\")\n\tlogLevel = flag.Int(\"log-level\", int(slog.LevelInfo), \"the log message level required to be logged\")\n\t// Backend options\n\tkeyStore = flag.String(\n\t\t\"key-store\",\n\t\tstring(Kubernetes),\n\t\t\"Public key repository implementation to use. Options: \"+strings.Join(keyStoreOpts, \",\"))\n\tk8sQPS   = flag.Int(\"k8s-qps\", 25, \"Limit of QPS to the Kubernetes API server.\")\n\tk8sBurst = flag.Int(\"k8s-burst\", 50, \"Burst limit of QPS to the Kubernetes API server.\")\n\t// API options\n\tbind     = flag.String(\"bind\", \"0.0.0.0\", \"Address to bind to\")\n\tport     = flag.Int(\"port\", 9090, \"Port number to listen on\")\n\tbasePath = flag.String(\"base\",\n\t\t\"/apis/core.token-vendor\",\n\t\t\"Base path where the API will be mounted to.\")\n\n\t// GCP Cloud options\n\tproject = flag.String(\"project\", \"\", \"The cloud project\")\n\n\t// Kubernetes backend options\n\tnamespace = flag.String(\"namespace\", \"default\",\n\t\t\"The namespace where to store the device keys. (Kubernetes)\")\n\n\t// Authentication / JWT options\n\tacceptedAudience = flag.String(\"accepted_audience\",\n\t\t\"\", \"Endpoint URL of the token vendor. Used for verification of JWTs send by robots.\")\n\tscopes    = scopeFlags{}\n\trobotName = flag.String(\"service_account\", \"robot-service\",\n\t\t\"Name of the service account to generate cloud access tokens for (unless specified per on-prem robot).\")\n)\n\nfunc main() {\n\tflag.Var(&scopes, \"scope\", \"GCP scopes included in the token given out to robots.\")\n\tflag.Parse()\n\n\tll := slog.Level(*logLevel)\n\tif *verbose {\n\t\tll = slog.LevelDebug\n\t}\n\tlogHandler := ilog.NewLogHandler(ll, os.Stderr)\n\tslog.SetDefault(slog.New(logHandler))\n\n\t// init components\n\tctx := context.Background()\n\tvar rep repository.PubKeyRepository\n\tvar err error\n\tif *keyStore == Kubernetes {\n\t\tconfig, err := rest.InClusterConfig()\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to get config\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tconfig.QPS = float32(*k8sQPS)\n\t\tconfig.Burst = *k8sBurst\n\t\tcs, err := kubernetes.NewForConfig(config)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to make clientset\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t\trep, err = k8s.NewK8sRepository(ctx, cs, *namespace)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to make k8s repository client\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t} else if *keyStore == Memory {\n\t\trep, err = memory.NewMemoryRepository(ctx)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to make in-memory repository\", ilog.Err(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t} else {\n\t\tslog.Error(\"unsupported key store option\", slog.String(\"Value\", *keyStore))\n\t\tos.Exit(1)\n\t}\n\tslog.Info(\"Set up key store\", slog.String(\"KeyStore\", *keyStore))\n\n\tverifier, err := oauth.NewTokenVerifier(ctx, &http.Client{}, *project)\n\tif err != nil {\n\t\tslog.Error(\"Failed to make verifier\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tts, err := tokensource.NewGCPTokenSource(ctx, nil, scopes)\n\tif err != nil {\n\t\tslog.Error(\"Failed to make token source\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tsaName := fmt.Sprintf(\"%s@%s.iam.gserviceaccount.com\", *robotName, *project)\n\ttv, err := app.NewTokenVendor(ctx, rep, verifier, ts, *acceptedAudience, saName)\n\tif err != nil {\n\t\tslog.Error(\"Failed to make token vendor\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\t// register API endpoints\n\tif err := api.Register(); err != nil {\n\t\tslog.Error(\"Failed to register root endpoints\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\tif err := apiv1.Register(tv, path.Join(*basePath, \"v1\")); err != nil {\n\t\tslog.Error(\"Failed to register v1 endpoints\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\n\t// serve API\n\taddr := fmt.Sprintf(\"%s:%d\", *bind, *port)\n\terr = api.SetupAndServe(addr)\n\tif err != nil {\n\t\tslog.Error(\"Failed to listen\", slog.String(\"IP\", addr), ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"cache.go\",\n        \"verifier.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@org_golang_google_api//iam/v1:go_default_library\",\n        \"@org_golang_google_api//option:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"cache_test.go\"],\n    embed = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/cache.go",
    "content": "package oauth\n\nimport (\n\t\"container/list\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype entry struct {\n\t// when the cache entry expires\n\texpires time.Time\n\t// the cache key\n\tkey string\n\t// if the key has actAs permission\n\tactAs bool\n}\n\ntype tokenCache struct {\n\t// hashmap for quick key lookup\n\tcache map[string]*list.Element\n\t// list of entries sorted by expire time, oldest in front\n\tevictOrder *list.List\n\t// mutex to serialize cache access\n\tmu sync.Mutex\n\t// expire an entry after\n\texpire time.Duration\n\t// maximum number of cache entries to keep\n\tsize int\n}\n\n// Creates a simple cache for the verifier logic with a maximum size.\n//\n// Eviction is done lazily during add. Existing entries are refreshed on add,\n// but not on access.\nfunc newTokenCache(size int, expire time.Duration) (*tokenCache, error) {\n\tif size < 1 {\n\t\treturn nil, fmt.Errorf(\"invalid cache size %d\", size)\n\t}\n\tif expire <= 0 {\n\t\treturn nil, fmt.Errorf(\"expire must be positive, got %v\", expire)\n\t}\n\treturn &tokenCache{cache: make(map[string]*list.Element, size),\n\t\tsize: size, expire: expire, evictOrder: list.New()}, nil\n}\n\n// add if a given token has `actAs` permission on a given service account.\n//\n// If the entry exists already, refresh it. Thread-safe.\nfunc (c *tokenCache) add(token Token, sa string, actAs bool) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\t// evict all expired entries\n\tc.evictExpired()\n\tkey := sa + string(token)\n\texpires := time.Now().Add(c.expire)\n\t// for existing keys update the entry\n\tif el, found := c.cache[key]; found {\n\t\te := el.Value.(*entry)\n\t\te.actAs = actAs\n\t\te.expires = expires\n\t\tc.evictOrder.MoveToBack(el)\n\t\treturn\n\t}\n\t// if we reached capacity, evict oldest element\n\tif len(c.cache) == c.size {\n\t\tc.evictOldest()\n\t}\n\t// create a new cache entry\n\te := &entry{expires: expires, key: key, actAs: actAs}\n\tel := c.evictOrder.PushBack(e)\n\tc.cache[key] = el\n}\n\n// Evict all expired keys (not thread-safe)\nfunc (c *tokenCache) evictExpired() {\n\tfor el := c.evictOrder.Front(); el != nil; el = c.evictOrder.Front() {\n\t\te := el.Value.(*entry)\n\t\tif e.expires.After(time.Now()) {\n\t\t\treturn\n\t\t}\n\t\tc.evictOrder.Remove(el)\n\t\tdelete(c.cache, e.key)\n\t}\n}\n\n// Evict oldest cache entry (not thread-safe)\nfunc (c *tokenCache) evictOldest() {\n\tfront := c.evictOrder.Front()\n\tif front == nil {\n\t\treturn\n\t}\n\te := front.Value.(*entry)\n\tc.evictOrder.Remove(front)\n\tdelete(c.cache, e.key)\n}\n\n// actAs queries the cache if a token has actAs permission on a service account.\n//\n// Returns (actAs, found). If found is false, token+sa was not found in cache\n// or the entry was already expired. Thread-safe.\nfunc (c *tokenCache) actAs(token Token, sa string) (bool, bool) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tkey := sa + string(token)\n\tif el, found := c.cache[key]; found {\n\t\te := el.Value.(*entry)\n\t\t// entry is already expired\n\t\tif e.expires.Before(time.Now()) {\n\t\t\treturn false, false\n\t\t}\n\t\treturn e.actAs, true\n\t}\n\treturn false, false\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/cache_test.go",
    "content": "package oauth\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype tokenProp struct {\n\ttoken Token\n\tacl   string\n\tactAs bool\n}\n\nvar (\n\tcounter   int\n\tcounterMu sync.Mutex\n)\n\nfunc randToken() *tokenProp {\n\tcounterMu.Lock()\n\tdefer counterMu.Unlock()\n\tcounter += 1\n\ttoken := Token(fmt.Sprintf(\"%X\", counter))\n\treturn &tokenProp{token: token, acl: fmt.Sprintf(\"%X\", counter),\n\t\tactAs: rand.Intn(2) == 1}\n}\n\nfunc TestTokenCacheOneTokenMultipleAcl(t *testing.T) {\n\ttc, err := newTokenCache(100, time.Hour)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\ttc.add(\"a\", \"acl_1\", true)\n\ttc.add(\"a\", \"acl_2\", false)\n\tactAs, found := tc.actAs(\"a\", \"acl_1\")\n\tif found == false || actAs != true {\n\t\tt.Fatalf(\"got (%v, %v), want (true, true)\", actAs, found)\n\t}\n\tactAs, found = tc.actAs(\"a\", \"acl_2\")\n\tif found == false || actAs != false {\n\t\tt.Fatalf(\"got (%v, %v), want (true, false)\", actAs, found)\n\t}\n}\n\nfunc TestTokenCacheExpire(t *testing.T) {\n\t// keys expire instantly\n\ttc, err := newTokenCache(100, time.Hour)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\ttc.expire *= -1\n\ttc.add(\"a\", \"acl_1\", true)\n\t_, found := tc.actAs(\"a\", \"acl_1\")\n\tif found == true {\n\t\tt.Fatalf(\"got key a, want not found\")\n\t}\n}\n\nfunc TestEvictExpired(t *testing.T) {\n\ttc, err := newTokenCache(100, time.Hour)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\ttc.expire *= -1\n\ttc.add(\"a\", \"acl_1\", true)\n\ttc.add(\"b\", \"acl_1\", true)\n\ttc.add(\"c\", \"acl_1\", true)\n\ttc.evictExpired()\n\tif len(tc.cache) != 0 || tc.evictOrder.Len() != 0 {\n\t\tt.Fatalf(\"got cache size %d and %d, want 0)\",\n\t\t\tlen(tc.cache), tc.evictOrder.Len())\n\t}\n}\n\nfunc TestEvictOldest(t *testing.T) {\n\ttc, err := newTokenCache(100, time.Hour)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\ttc.add(\"a\", \"acl_1\", true)\n\ttc.add(\"b\", \"acl_1\", true)\n\ttc.add(\"c\", \"acl_1\", true)\n\ttc.evictOldest()\n\tif len(tc.cache) != 2 || tc.evictOrder.Len() != 2 {\n\t\tt.Fatalf(\"got cache size got %d and %d after eviction, want 2\",\n\t\t\tlen(tc.cache), tc.evictOrder.Len())\n\t}\n\te := tc.evictOrder.Front().Value.(*entry)\n\tif e.key != \"acl_1b\" {\n\t\tt.Fatalf(\"got oldest element %v after eviction, want b\", e)\n\t}\n}\n\n// Similar to test case TestEvictOldest, but without explicit eviction.\nfunc TestEvictionGeneral(t *testing.T) {\n\ttc, err := newTokenCache(2, time.Hour)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\ttc.add(\"a\", \"acl_1\", true)\n\ttc.add(\"b\", \"acl_1\", true)\n\ttc.add(\"c\", \"acl_1\", true)\n\tif len(tc.cache) != 2 || tc.evictOrder.Len() != 2 {\n\t\tt.Fatalf(\"got cache size got %d and %d after eviction, want 2\",\n\t\t\tlen(tc.cache), tc.evictOrder.Len())\n\t}\n\te := tc.evictOrder.Front().Value.(*entry)\n\tif e.key != \"acl_1b\" {\n\t\tt.Fatalf(\"got oldest element %v after eviction, want b\", e)\n\t}\n}\n\nfunc TestParallelAccessNoEviction(t *testing.T) {\n\tconst workers = 3\n\tconst entries = 10000\n\ttc, err := newTokenCache(workers*entries, time.Hour)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\tvar wg sync.WaitGroup\n\terrs := make(chan error, workers)\n\tfor i := 0; i < workers; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\terr := accessWorker(tc, entries, true)\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\tif len(errs) > 0 {\n\t\terr = <-errs\n\t\tif err != nil {\n\t\t\tt.Fatal(err.Error())\n\t\t}\n\t}\n}\n\nfunc accessWorker(tc *tokenCache, adds int, failIfNotFound bool) error {\n\t// create a bunch of entries and put into cache\n\ttokens := make([]*tokenProp, 0, adds)\n\tfor i := 0; i < adds; i++ {\n\t\tt := randToken()\n\t\ttokens = append(tokens, t)\n\t\ttc.add(t.token, t.acl, t.actAs)\n\t}\n\t// verify entries in the cache\n\tfor idx, t := range tokens {\n\t\tactAs, found := tc.actAs(t.token, t.acl)\n\t\tif found == false && failIfNotFound {\n\t\t\treturn fmt.Errorf(\"token %d: entry %v should be there, but is not\", idx, t)\n\t\t}\n\t\tif !found {\n\t\t\tcontinue\n\t\t}\n\t\tif actAs != t.actAs {\n\t\t\treturn fmt.Errorf(\"actAs got %v, want %v\", actAs, t.actAs)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/jwt/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"jwt.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/oauth/jwt\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_form3tech_oss_jwt_go//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"jwt_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\"@com_github_google_go_cmp//cmp:go_default_library\"],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/jwt/jwt.go",
    "content": "package jwt\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\tjwt \"github.com/form3tech-oss/jwt-go\"\n\t\"github.com/pkg/errors\"\n)\n\ntype payload struct {\n\tAud    string\n\tIss    string\n\tExp    int64\n\tSub    string\n\tScopes string\n\tClaims string\n}\n\n// PayloadUnsafe returns the unverified payload section of a given JWT.\n//\n// Unsafe because the content can not be trusted if you do not also verify\n// the signature of the JWT.\nfunc PayloadUnsafe(jwtk string) (*payload, error) {\n\tparts := strings.Split(jwtk, \".\")\n\tif len(parts) != 3 {\n\t\treturn nil, errors.New(\"invalid JWT, token must have 3 parts\")\n\t}\n\tpayloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to decode JWT payload section\")\n\t}\n\tdat := payload{}\n\terr = json.Unmarshal(payloadBytes, &dat)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to unmarshal JWT payload section\")\n\t}\n\treturn &dat, nil\n}\n\n// Verify the given encoded JWT with the RSA public key in PEM format.\nfunc VerifySignature(jwtk string, pubKey string) error {\n\tkey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(pubKey))\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to parse public key\")\n\t}\n\t_, err = jwt.Parse(jwtk, func(t *jwt.Token) (interface{}, error) {\n\t\tif _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {\n\t\t\treturn nil, errors.New(\"unexpected signing method, only RSA family is accepted\")\n\t\t}\n\t\treturn key, nil\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to parse and verify signature\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/jwt/jwt_test.go",
    "content": "package jwt\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\n/*\n# Valid token\nHeader:\n{\n\n\t\"alg\": \"RS256\",\n\t\"typ\": \"JWT\"\n\n}\nPayload:\n{\n\n\t\"aud\": \"testaud\",\n\t\"iss\": \"robot-dev-testuser\",\n\t\"exp\": 1913373010,\n\t\"scopes\": \"testscopes\",\n\t\"claims\": \"testclaims\"\n\n}\nSigned using `testPrivKey`\n*/\n\nvar testPayload = payload{\n\tAud:    \"testaud\",\n\tIss:    \"robot-dev-testuser\",\n\tExp:    1913373010,\n\tScopes: \"testscopes\",\n\tClaims: \"testclaims\",\n}\n\nconst testJWT = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjoxOTEzMzczMDEwLCJzY29wZXMiOiJ0ZXN0c2NvcGVzIiwiY2xhaW1zIjoidGVzdGNsYWltcyJ9.WJP0shiqynW9ZrmV4k78W3_nn_YA86XLK58IJYyqUF-8LAG92MraNqVqD0t6i-s90VBL64hCXlsA7zP3WlsMHOEvXCyRkGffhbJNIlJqIVTVfGvyF-ZmuaAr352n5kmKTrfTRi7h9LWTcvDgSosN438J8Jy9BT1FE9P-BHfyBUegZ15DWFAiAhz0r_Fgj7hAMXUnRdZfj3_dE0Nhi5IGs3L-0XzU-dE150ZJvtGMdIjc_QCqYHV3wtSgETKDYQoonD08n6g5GqC8nNkqrWFMttafLdPaDAsr8KWtj1dD1w9sw1YJClEzF9JOc63WNPZf8CgdU2enFW-V-2vHbUaekg\"\n\nconst testPubKey = `\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C\n2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sM\ngyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxX\nvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmz\nQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard\n17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xD\npwIDAQAB\n-----END PUBLIC KEY-----`\n\n/*\nUnused in key right now, but here for reference:\nconst testPrivKey = `\n-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3\nOJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/\nyfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pk\ndXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22\naCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsF\neZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQABAoH/bKMLrT/W4/wT+6PN\nKU3FVbWDompywyssqlZ31Q6g9pdCCTIyw0jemlG0ewtdk3yIu8WS0Aku36NudWtP\npvDBPo+CZILRYS9N0AUNXBPl7sUA4OzVdCBnk5FTF1daV7N5CA+ZDXuDVa91fduJ\n1ElSF9+weCKph0170Rsc74G570Q1ypoee/gdhkwwK5aYfTs+Z6fpaEnHaPzcwYkF\n4QTsCshtoGZslmgZt8Tm7sfDDFWD20fmr1s350Ne1I7VYRFiyGbQI+IB+4pc9LSX\n8CHcHIzHidKYTSG6YwpDsNRN/BkQklhsuLnNacMFFddO0IHIS0GlLBJbCRkN3b/n\n/XC5AoGBAPZIN3VCpSEAw6OsM1zL4CBcq2dOb5b87rAeUmSkmW415fuyUNJJBcaf\n1pliCQNeg9RzRDuHOs6BTU9i+fLcbOwSapFzGxzqnv4xmkHbj1Xs52Z+97HvKKld\nxlQ/TF72WGITZVwmQWxJ9Rgx+bi7OirzOtQYoNpFoF5vHgyGrUZ7AoGBAMSosXUk\nuLMzrZjH4Oetp8tq9Udyk7Xkk7booU7I0iPb/Dvadsuc9WZI+LP4R3iWmtLcJOUr\nWyfliCLvbWtF4aW2vo7hvffe19krg/H26WEuBTuQGCZv8B5o8xHSecb7jbrKt9g6\nr8I5kr+2tAZKLC6mtFdJgfSXNO9tveBxe+XFAoGBAIwQljnCJVeXr6wuCygDavv8\nuB6QpTYhsz3GgOVsFzZuwNVcnEp77SUBUnL5JlccMa1pwKx6RB+dufIkQDK22duI\nvcLqy8iuRq4aV7iMvgAIM7I/E2/GrEFma50OQsjfIXTlwwedWifUB+gyw+sjz/kN\nS6/EMfbxEjuixlwpW/JxAoGBAKG5dM44F6hPPFijL0J3XcD8QZ+zCuQPiKZnopgO\nsDmLJF/4Za9Gccze/5/I8sWpXMNBBRptUDZ8HTtVmK8aNdm4cfdAj5/y46EVlxl6\nCyy+0tDLzAB4F4h6mEI0y66mmkRdh1jL0lQwUo1Ua7Gsd68Zqr8JlVSWsJKhtf+I\nc/JdAoGAFCSDby7ByX0W23Su3R28+9lWRSmNG79kLRLzlXsCwXTUTFh/TjAaEKgK\nvwi8dtCSMNnJLCUXGx5cjTndgjTl8Woah0wy9XNNeIUjI8JPxIwXmmjppPKdCBI4\n0ZyqQjgPJvwfY7lxFjE10ypv99QDlEbnwngt6bvSkY+6+DQTUDw=\n-----END RSA PRIVATE KEY-----\n`\n*/\n\nfunc TestVerifySignature(t *testing.T) {\n\terr := VerifySignature(testJWT, testPubKey)\n\tif err != nil {\n\t\tt.Fatalf(\"VerifySignature(..): valid JWT failed verifiy, got: %v\", err)\n\t}\n}\n\nfunc TestPayloadUnsafe(t *testing.T) {\n\tp, err := PayloadUnsafe(testJWT)\n\tif err != nil {\n\t\tt.Fatalf(\"PayloadUnsafe(..): valid payload failed, got %v\", err)\n\t}\n\tif diff := cmp.Diff(p, &testPayload); diff != \"\" {\n\t\tt.Fatalf(\"PayloadUnsafe(..): got %+v, want %+v, diff: %v\", p, testPayload, diff)\n\t}\n}\n\nfunc TestPayloadUnsafeInvalidPayload(t *testing.T) {\n\t// insert junk into the payload part\n\tinvalidJWT := testJWT[:80] + \"Z\" + testJWT[80:]\n\tp, err := PayloadUnsafe(invalidJWT)\n\tif err == nil { // no error\n\t\tt.Fatalf(\"PayloadUnsafe(..) should have errored for invalid input, got payload %+v\", p)\n\t}\n}\n\nfunc TestVerifySignatureInvalidSig(t *testing.T) {\n\t// insert junk into the signature part\n\tinvalidSig := testJWT[:230] + \"Z\" + testJWT[230:]\n\terr := VerifySignature(invalidSig, testPubKey)\n\tif err == nil { // no error\n\t\tt.Fatal(\"VerifySignature(..): should have errored with invalid signature, but did not\")\n\t}\n}\n\n/*\n# Invalid signature algorithm\nThe following token uses `HS256` algorithm with the secret `somesecret`.\n*/\n\nconst testJWTInvalidSigAlg = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0YXVkIiwiaXNzIjoicm9ib3QtZGV2LXRlc3R1c2VyIiwiZXhwIjoxOTEzMzczMDEwLCJzY29wZXMiOiJ0ZXN0c2NvcGVzIiwiY2xhaW1zIjoidGVzdGNsYWltcyJ9.dXmTXpf3gS12z-Jkkw3ZTttvCxymqh03iCRd77DZCjE\"\n\nfunc TestVerifySignatureInvalidSigAlg(t *testing.T) {\n\t// we use the public key as second parameter because we know it does parse the\n\t// key first before checking the signature algorithm\n\terr := VerifySignature(testJWTInvalidSigAlg, testPubKey)\n\tif err == nil { // no error\n\t\tt.Fatal(\"VerifySignature(..): should have errored with invalid signature alg, but did not\")\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/oauth/verifier.go",
    "content": "package oauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"google.golang.org/api/iam/v1\"\n\t\"google.golang.org/api/option\"\n)\n\ntype TokenVerifier struct {\n\ts       *iam.Service\n\tproject string\n\tcache   *tokenCache\n}\n\n// Token as string alias empathizes that this is a secret.\ntype Token string\n\nconst (\n\tcacheSize   = 1000\n\tcacheExpire = 5 * time.Minute\n)\n\n// NewTokenVerifier returns a new TokenVerifier instance for a cloud project.\nfunc NewTokenVerifier(ctx context.Context, c *http.Client, project string) (*TokenVerifier, error) {\n\ts, err := iam.NewService(ctx, option.WithHTTPClient(c))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to create service client\")\n\t}\n\ttc, err := newTokenCache(cacheSize, cacheExpire)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to create token cache\")\n\t}\n\treturn &TokenVerifier{s: s, project: project, cache: tc}, nil\n}\n\n// Verify if a given token has \"actAs\" permission on a given service account.\nfunc (v *TokenVerifier) Verify(ctx context.Context, token Token, sa string) error {\n\tresource := fmt.Sprintf(\"projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com\", v.project, sa, v.project)\n\tconst iamActAs = \"iam.serviceAccounts.actAs\"\n\t// check the cache first\n\tactAs, found := v.cache.actAs(token, resource)\n\tif found && actAs {\n\t\treturn nil\n\t}\n\tif found && !actAs {\n\t\treturn fmt.Errorf(\"token is missing permission %q for resource %q (cached)\",\n\t\t\tiamActAs, resource)\n\t}\n\t// query IAM if not found in cache\n\tts := time.Now()\n\tresp, err := doTestIamPermissions(ctx, v.s, string(token), resource, []string{iamActAs})\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"TestIamPermissions failed for resource %q with permission %q after %.3fs\",\n\t\t\tresource, iamActAs, time.Since(ts).Seconds())\n\t}\n\tif !contains(resp.Permissions, iamActAs) {\n\t\tv.cache.add(token, resource, false)\n\t\treturn fmt.Errorf(\"token is missing permission %q for resource %q\", iamActAs, resource)\n\t}\n\tv.cache.add(token, resource, true)\n\treturn nil\n}\n\n// doTestIamPermissions retry parameters\nconst (\n\ttimeout       = time.Second * 5\n\tretryInterval = time.Second * 1\n\tretries       = 2\n)\n\nfunc doTestIamPermissions(ctx context.Context, s *iam.Service, token, resource string, permissions []string) (*iam.TestIamPermissionsResponse, error) {\n\tpreq := iam.TestIamPermissionsRequest{Permissions: permissions}\n\tpcall := s.Projects.ServiceAccounts.TestIamPermissions(resource, &preq)\n\tpcall.Header().Set(\"Authorization\", \"Bearer \"+string(token))\n\n\tfor i := 0; i <= retries; i++ {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tdefault:\n\t\t}\n\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\tdefer cancel()\n\t\tr, err := pcall.Context(ctx).Do()\n\t\tif err == nil { // no error, return response\n\t\t\treturn r, nil\n\t\t}\n\t\t// continue/retry only on DeadlineExceeded errors\n\t\tif !errors.Is(err, context.DeadlineExceeded) {\n\t\t\treturn nil, err\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn r, ctx.Err()\n\t\t// static retry interval\n\t\tcase <-time.After(retryInterval):\n\t\t}\n\t}\n\treturn nil, errors.New(\"\")\n}\n\nfunc contains(s []string, str string) bool {\n\tfor _, v := range s {\n\t\tif v == str {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"repository.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/k8s/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"k8s.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository/k8s\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"k8s_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/fake:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/k8s/k8s.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/cache\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n\t\"github.com/googlecloudrobotics/ilog\"\n)\n\n// Re-list all ConfigMaps periodically. If this causes problems, consider\n// setting to 0 instead to disable, but hopefully this provides provides\n// some defense against bugs without being too costly.\nconst resyncPeriod = 1 * time.Hour\n\n// K8sRepository uses Kubernetes configmaps as public key backend for devices.\ntype K8sRepository struct {\n\tkcl kubernetes.Interface // client-go Clientset\n\tns  string               // The namespace to use\n\n\tcmInformer cache.SharedIndexInformer\n}\n\n// NewK8sRepository creates a new K8sRepository key repository.\n//\n// Use `ns` to specify an existing namespace to use for the device configmaps. Provide\n// either a k8s.io/client-go/kubernetes/fake.NewSimpleClientset() for `kcl`\n// for testing, or a real Interface from kubernetes.NewForConfig(..).\nfunc NewK8sRepository(ctx context.Context, kcl kubernetes.Interface, ns string) (*K8sRepository, error) {\n\t// The informer provides an in-memory cache and prevents us from hammering the apiserver.\n\tcmInformer := cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options metav1.ListOptions) (object runtime.Object, e error) {\n\t\t\t\treturn kcl.CoreV1().ConfigMaps(ns).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFunc: func(options metav1.ListOptions) (i watch.Interface, e error) {\n\t\t\t\treturn kcl.CoreV1().ConfigMaps(ns).Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\t&corev1.ConfigMap{},\n\t\tresyncPeriod,\n\t\tcache.Indexers{},\n\t)\n\tgo cmInformer.Run(ctx.Done())\n\t// Wait for the cache to sync before returning so we don't serve requests\n\t// until we're ready.\n\tif !cache.WaitForCacheSync(ctx.Done(), cmInformer.HasSynced) {\n\t\treturn nil, fmt.Errorf(\"failed to sync configmap cache\")\n\t}\n\treturn &K8sRepository{kcl: kcl, ns: ns, cmInformer: cmInformer}, nil\n}\n\nconst (\n\tpubKey = \"pubKey\" // Configmap key for the public key\n\t// Configmap annotation specifies the service account to use (optional)\n\tserviceAccountAnnotation = \"cloudrobotics.com/gcp-service-account\"\n\t// Configmap annotation specifies the intermediate service account delegate to use (optional)\n\tserviceAccountDelegateAnnotation = \"cloudrobotics.com/gcp-service-account-delegate\"\n)\n\n// ListAllDeviceIDs returns a slice of all device identifiers found in the namespace.\nfunc (k *K8sRepository) ListAllDeviceIDs(ctx context.Context) ([]string, error) {\n\tobjs := k.cmInformer.GetStore().List()\n\tnames := make([]string, 0)\n\tfor _, obj := range objs {\n\t\tcm, ok := obj.(*corev1.ConfigMap)\n\t\tif ok {\n\t\t\tnames = append(names, cm.GetName())\n\t\t}\n\t}\n\treturn names, nil\n}\n\n// LookupKey returns the public key for a given device identifier.\n//\n// The public key is stored under a specific key in the configmap. Returns an\n// error if the configmap is not found or is not valid.\nfunc (k *K8sRepository) LookupKey(ctx context.Context, deviceID string) (*repository.Key, error) {\n\tslog.Debug(\"looking up public key\", slog.String(\"Namespace\", k.ns), slog.String(\"ConfigMap\", deviceID))\n\tobj, exists, err := k.cmInformer.GetStore().GetByKey(k.ns + \"/\" + deviceID)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to retrieve configmap %q/%q from cache\", k.ns, deviceID)\n\t}\n\tif !exists {\n\t\treturn nil, errors.Wrapf(repository.ErrNotFound, \"failed to retrieve configmap %q/%q\", k.ns, deviceID)\n\t}\n\tcm, ok := obj.(*corev1.ConfigMap)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unexpected object type: %T\", obj)\n\t}\n\n\tkey, found := cm.Data[pubKey]\n\tif !found {\n\t\treturn nil, fmt.Errorf(\"configmap %q/%q does not contain key %q\", k.ns, deviceID, pubKey)\n\t}\n\tsa := cm.ObjectMeta.Annotations[serviceAccountAnnotation]\n\tsaDelegate := cm.ObjectMeta.Annotations[serviceAccountDelegateAnnotation]\n\treturn &repository.Key{key, sa, saDelegate}, nil\n}\n\n// PublishKey sets or updates a public key for a given device identifier.\n//\n// If the configmap for a device does not exist yet it is created. If it exists\n// already the public key section of the configmap is updated.\nfunc (k *K8sRepository) PublishKey(ctx context.Context, deviceID, publicKey string) error {\n\tslog.Debug(\"publishing key\", slog.String(\"DeviceID\", deviceID))\n\tcm, err := createPubKeyDeviceConfig(deviceID, k.ns, publicKey)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to init device configmap %q/%q\", k.ns, deviceID)\n\t}\n\t_, err = k.kcl.CoreV1().ConfigMaps(k.ns).Create(ctx, cm, metav1.CreateOptions{})\n\tif err == nil { // no error\n\t\t// Add to the informer store so that LookupKey can be used immediately.\n\t\tif err := k.cmInformer.GetStore().Add(cm); err != nil {\n\t\t\tslog.Warn(\"failed to add to informer store\", slog.String(\"DeviceID\", deviceID), ilog.Err(err))\n\t\t}\n\t\treturn nil\n\t}\n\tif !kerrors.IsAlreadyExists(err) { // any error not AlreadyExist\n\t\treturn errors.Wrapf(err, \"failed to create device configmap %q/%q\", k.ns, deviceID)\n\t}\n\t// AlreadyExist error, updating configmap.\n\t// We do not want to override any other keys besides the public key here.\n\t// createPubKeyDeviceConfig only creates a minimum configmap so updating is safe here.\n\tif _, err := k.kcl.CoreV1().ConfigMaps(k.ns).Update(ctx, cm, metav1.UpdateOptions{}); err != nil {\n\t\treturn errors.Wrapf(err, \"configmap %q/%q exists but failed to update it\", k.ns, deviceID)\n\t}\n\t// Update the informer store so that LookupKey can be used immediately.\n\tif err := k.cmInformer.GetStore().Update(cm); err != nil {\n\t\tslog.Warn(\"failed to update informer store\", slog.String(\"DeviceID\", deviceID), ilog.Err(err))\n\t}\n\treturn nil\n}\n\nfunc (k *K8sRepository) ConfigureKey(ctx context.Context, deviceID string, opts repository.KeyOptions) error {\n\tcm, err := k.kcl.CoreV1().ConfigMaps(k.ns).Get(ctx, deviceID, metav1.GetOptions{})\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn errors.Wrapf(repository.ErrNotFound, \"failed to retrieve configmap %q/%q\", k.ns, deviceID)\n\t\t}\n\t\treturn errors.Wrapf(err, \"failed to retrieve configmap %q/%q from cache\", k.ns, deviceID)\n\t}\n\tif cm.ObjectMeta.Annotations == nil {\n\t\tcm.ObjectMeta.Annotations = make(map[string]string)\n\t}\n\tmapSetOrDelete(cm.ObjectMeta.Annotations, serviceAccountAnnotation, opts.ServiceAccount)\n\tmapSetOrDelete(cm.ObjectMeta.Annotations, serviceAccountDelegateAnnotation, opts.ServiceAccountDelegate)\n\tif _, err := k.kcl.CoreV1().ConfigMaps(k.ns).Update(ctx, cm, metav1.UpdateOptions{}); err != nil {\n\t\treturn errors.Wrapf(err, \"failed to update configmap %q/%q\", k.ns, deviceID)\n\t}\n\t// Update the informer store so that LookupKey can be used immediately.\n\tif err := k.cmInformer.GetStore().Update(cm); err != nil {\n\t\tslog.Warn(\"failed to update informer store\", slog.String(\"DeviceID\", deviceID), ilog.Err(err))\n\t}\n\treturn nil\n}\n\n// createPubKeyDeviceConfig creates a configmap with only the public key in it.\n//\n// This is used also during update of existing devices. Make sure no default values\n// are used here which could override a manually set key.\nfunc createPubKeyDeviceConfig(name, namespace, pk string) (*corev1.ConfigMap, error) {\n\treturn &corev1.ConfigMap{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"ConfigMap\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tName:      name,\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"app.kubernetes.io/managed-by\": \"token-vendor\",\n\t\t\t},\n\t\t},\n\t\tData: map[string]string{pubKey: pk},\n\t}, nil\n}\n\nfunc mapSetOrDelete(m map[string]string, k, v string) {\n\tif v != \"\" {\n\t\tm[k] = v\n\t} else {\n\t\tdelete(m, k)\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/k8s/k8s_test.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"k8s.io/client-go/kubernetes/fake\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n)\n\n// Publish a key, retrieve it again and check listing of all keys.\nfunc TestPublishListLookup(t *testing.T) {\n\tctx := context.Background()\n\tcs := fake.NewSimpleClientset()\n\tkcl, err := NewK8sRepository(ctx, cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconst id = \"testdevice\"\n\tconst key = \"testkey\"\n\tif err = kcl.PublishKey(ctx, id, key); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err = kcl.LookupKey(ctx, id); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdevices, err := kcl.ListAllDeviceIDs(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(devices) != 1 || devices[0] != id {\n\t\tt.Fatalf(`ListAllDeviceIDs() = %v, want [%q]`, devices, id)\n\t}\n}\n\n// Publish a key and override it with another one.\nfunc TestPublishKeyUpdate(t *testing.T) {\n\tctx := context.Background()\n\tcs := fake.NewSimpleClientset()\n\tkcl, err := NewK8sRepository(ctx, cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconst id = \"testdevice\"\n\tconst key2 = \"testkey2\"\n\tif err = kcl.PublishKey(ctx, id, \"testkey\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err = kcl.PublishKey(ctx, id, key2); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := kcl.LookupKey(ctx, id)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif k.PublicKey != key2 {\n\t\tt.Fatalf(\"LookupKey(..) = %q, want %q\", k.PublicKey, key2)\n\t}\n}\n\nfunc TestLookupDoesNotExist(t *testing.T) {\n\tctx := context.Background()\n\tcs := fake.NewSimpleClientset()\n\tkcl, err := NewK8sRepository(ctx, cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := kcl.LookupKey(ctx, \"testdevice\")\n\tif !errors.Is(err, repository.ErrNotFound) {\n\t\tt.Fatalf(\"LookupKey produced wrong error: got %v, want %v\", err, repository.ErrNotFound)\n\t}\n\tif k != nil {\n\t\tt.Fatalf(\"LookupKey(..) = %q, want nil\", k)\n\t}\n}\n\nfunc TestConfigure(t *testing.T) {\n\tctx := context.Background()\n\tcs := fake.NewSimpleClientset()\n\tkcl, err := NewK8sRepository(ctx, cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconst id = \"testdevice\"\n\tconst key = \"testkey\"\n\tif err = kcl.PublishKey(ctx, id, key); err != nil {\n\t\tt.Fatal(err)\n\t}\n\topts := repository.KeyOptions{\"svc@example.com\", \"\"}\n\tif err := kcl.ConfigureKey(ctx, id, opts); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := kcl.LookupKey(ctx, id)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif k.SAName != \"svc@example.com\" {\n\t\tt.Fatalf(\"LookupKey: got %q, expected %q\", k.SAName, \"svc@example.com\")\n\t}\n}\n\nfunc TestReConfigure(t *testing.T) {\n\tctx := context.Background()\n\tcs := fake.NewSimpleClientset()\n\tkcl, err := NewK8sRepository(ctx, cs, \"default\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconst id = \"testdevice\"\n\tconst key = \"testkey\"\n\tif err = kcl.PublishKey(ctx, id, key); err != nil {\n\t\tt.Fatal(err)\n\t}\n\topts := repository.KeyOptions{\"svc@example.com\", \"\"}\n\tif err := kcl.ConfigureKey(ctx, id, opts); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// remove the config again\n\topts = repository.KeyOptions{\"\", \"\"}\n\tif err := kcl.ConfigureKey(ctx, id, opts); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := kcl.LookupKey(ctx, id)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif k.SAName != \"\" {\n\t\tt.Fatalf(\"LookupKey: got %q, expected %q\", k.SAName, \"svc@example.com\")\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/memory/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"memory.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository/memory\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/cmd/token-vendor/repository:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"memory_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\"//src/go/cmd/token-vendor/repository:go_default_library\"],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/memory/memory.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage memory\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n)\n\n// MemoryRepository uses a in-memory datastructure to store the keys.\n// Used only for integration tests.\ntype MemoryRepository struct {\n\tkeys map[string]string\n\topts map[string]repository.KeyOptions\n}\n\nfunc NewMemoryRepository(ctx context.Context) (*MemoryRepository, error) {\n\treturn &MemoryRepository{\n\t\tkeys: map[string]string{},\n\t\topts: map[string]repository.KeyOptions{},\n\t}, nil\n}\n\nfunc (m *MemoryRepository) PublishKey(ctx context.Context, deviceID, publicKey string) error {\n\tslog.Debug(\"PublishKey\", slog.String(\"DeviceID\", deviceID), slog.String(\"PublicKey\", publicKey))\n\tm.keys[deviceID] = publicKey\n\treturn nil\n}\n\nfunc (m *MemoryRepository) LookupKey(ctx context.Context, deviceID string) (*repository.Key, error) {\n\tslog.Debug(\"LookupKey\", slog.String(\"DeviceID\", deviceID))\n\t// key not found does not need to be an error\n\tk, found := m.keys[deviceID]\n\tif !found {\n\t\treturn nil, repository.ErrNotFound\n\t}\n\topts, found := m.opts[deviceID]\n\tif !found {\n\t\topts = repository.KeyOptions{}\n\t}\n\treturn &repository.Key{k, opts.ServiceAccount, opts.ServiceAccountDelegate}, nil\n}\n\nfunc (m *MemoryRepository) ConfigureKey(ctx context.Context, deviceID string, opts repository.KeyOptions) error {\n\tm.opts[deviceID] = opts\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/memory/memory_test.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage memory\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/repository\"\n)\n\n// Test publish and lookup key\nfunc TestPublishAndLookup(t *testing.T) {\n\tctx := context.Background()\n\tm, err := NewMemoryRepository(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := m.PublishKey(ctx, \"a\", \"akey\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := m.PublishKey(ctx, \"b\", \"bkey\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := m.LookupKey(ctx, \"a\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif k.PublicKey != \"akey\" {\n\t\tt.Fatalf(\"Key for a: got %q, want %q\", k, \"akey\")\n\t}\n\tk, err = m.LookupKey(ctx, \"b\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif k.PublicKey != \"bkey\" {\n\t\tt.Fatalf(\"Key for b: got %q, want %q\", k, \"bkey\")\n\t}\n}\n\nfunc TestLookupNotFound(t *testing.T) {\n\tctx := context.Background()\n\tm, err := NewMemoryRepository(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := m.LookupKey(ctx, \"a\")\n\tif !errors.Is(err, repository.ErrNotFound) {\n\t\tt.Fatalf(\"LookupKey produced wrong error: got %v, want %v\", err, repository.ErrNotFound)\n\t}\n\tif k != nil {\n\t\tt.Fatalf(\"LookupKey: got %q, expected empty response\", k)\n\t}\n}\n\nfunc TestConfigure(t *testing.T) {\n\tctx := context.Background()\n\tm, err := NewMemoryRepository(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := m.PublishKey(ctx, \"a\", \"akey\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\topts := repository.KeyOptions{\"svc@example.com\", \"\"}\n\tif err := m.ConfigureKey(ctx, \"a\", opts); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tk, err := m.LookupKey(ctx, \"a\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif k.SAName != \"svc@example.com\" {\n\t\tt.Fatalf(\"LookupKey: got %q, expected %q\", k.SAName, \"svc@example.com\")\n\t}\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/repository/repository.go",
    "content": "// Copyright 2024 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package repository defines the api for the pub key stores\npackage repository\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\nvar (\n\t// ErrNotFound indicates that the requested key is not known.\n\tErrNotFound = errors.New(\"key not found\")\n)\n\n// Key holds data + metadata of a public key entry\ntype Key struct {\n\t// PublicKey contains the public key data\n\tPublicKey string\n\t// SAName is the optional GCP IAM service-account that has been associated.\n\tSAName string\n\t// SADelegateName is the optional GCP IAM service-account to act as an intermediate delegate\n\tSADelegateName string\n}\n\n// KeyOptions contain optional settings for a key\ntype KeyOptions struct {\n\tServiceAccount         string `json:\"service-account\"`\n\tServiceAccountDelegate string `json:\"service-account-delegate\"`\n}\n\n// PubKeyRepository defines the api for the pub key stores\ntype PubKeyRepository interface {\n\t// LookupKey retrieves the public key of a device from the repository.\n\t// An empty string return indicates that no key exists for the given identifier or\n\t// that the device is blocked.\n\tLookupKey(ctx context.Context, deviceID string) (*Key, error)\n\tPublishKey(ctx context.Context, deviceID, publicKey string) error\n\t// ConfigureKey applies the given opts to the key store.\n\tConfigureKey(ctx context.Context, deviceID string, opts KeyOptions) error\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/testdata/describe_device_a.json",
    "content": "{\n    \"id\": \"robot-dev-device-a\",\n    \"name\": \"projects/testproject/locations/europe-west1/registries/cloud-robotics/devices/3072877074145970\",\n    \"numId\": \"3072877074145970\",\n    \"credentials\": [\n      {\n        \"publicKey\": {\n          \"format\": \"RSA_PEM\",\n          \"key\": \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQAB\\n-----END PUBLIC KEY-----\"\n        },\n        \"expirationTime\": \"1970-01-01T00:00:00Z\"\n      }\n    ],\n    \"config\": {\n      \"version\": \"1\",\n      \"cloudUpdateTime\": \"2022-08-18T15:36:53.627428Z\"\n    },\n    \"gatewayConfig\": {}\n  }"
  },
  {
    "path": "src/go/cmd/token-vendor/testdata/describe_device_b.json",
    "content": "{\n    \"id\": \"robot-dev-device-b\",\n    \"name\": \"projects/testproject/locations/europe-west1/registries/cloud-robotics/devices/3072877074145970\",\n    \"numId\": \"3072877074145970\",\n    \"credentials\": [\n      {\n        \"publicKey\": {\n          \"format\": \"RSA_PEM\",\n          \"key\": \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQAB\\n-----END PUBLIC KEY-----\"\n        },\n        \"expirationTime\": \"1970-01-01T00:00:00Z\"\n      }\n    ],\n    \"config\": {\n      \"version\": \"1\",\n      \"cloudUpdateTime\": \"2022-08-18T15:36:53.627428Z\"\n    },\n    \"gatewayConfig\": {}\n  }"
  },
  {
    "path": "src/go/cmd/token-vendor/testdata/describe_device_b_blocked.json",
    "content": "{\n    \"id\": \"robot-dev-device-b\",\n    \"name\": \"projects/testproject/locations/europe-west1/registries/cloud-robotics/devices/3072877074145970\",\n    \"numId\": \"3072877074145970\",\n    \"blocked\": true,\n    \"credentials\": [\n      {\n        \"publicKey\": {\n          \"format\": \"RSA_PEM\",\n          \"key\": \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQAB\\n-----END PUBLIC KEY-----\"\n        },\n        \"expirationTime\": \"1970-01-01T00:00:00Z\"\n      }\n    ],\n    \"config\": {\n      \"version\": \"1\",\n      \"cloudUpdateTime\": \"2022-08-18T15:36:53.627428Z\"\n    },\n    \"gatewayConfig\": {}\n  }"
  },
  {
    "path": "src/go/cmd/token-vendor/testdata/list_devices.json",
    "content": "{\n    \"devices\": [\n        {\n            \"id\": \"testdevice-a\",\n            \"name\": \"projects/testproject/locations/testlocation/registries/testregistry/devices/1\",\n            \"numId\": \"1\",\n            \"gatewayConfig\": {}\n        },\n        {\n            \"id\": \"testdevice-b\",\n            \"name\": \"projects/testproject/locations/testlocation/registries/testregistry/devices/2\",\n            \"numId\": \"2\",\n            \"gatewayConfig\": {}\n        }\n    ]\n}"
  },
  {
    "path": "src/go/cmd/token-vendor/tokensource/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"gcp.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/cmd/token-vendor/tokensource\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@com_google_cloud_go_compute_metadata//:go_default_library\",\n        \"@org_golang_google_api//iamcredentials/v1:go_default_library\",\n        \"@org_golang_google_api//option:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"gcp_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"@com_github_google_go_cmp//cmp:go_default_library\",\n        \"@org_golang_google_api//iamcredentials/v1:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/cmd/token-vendor/tokensource/gcp.go",
    "content": "package tokensource\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"math\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"cloud.google.com/go/compute/metadata\"\n\t\"github.com/pkg/errors\"\n\tiam \"google.golang.org/api/iamcredentials/v1\"\n\t\"google.golang.org/api/option\"\n)\n\ntype GCPTokenSource struct {\n\tservice *iam.Service\n\tscopes  []string\n}\n\ntype TokenResponse struct {\n\tAccessToken string `json:\"access_token\"`\n\tExpiresIn   int64  `json:\"expires_in\"`\n\tScope       string `json:\"scope\"`\n\tTokenType   string `json:\"token_type\"`\n}\n\nconst (\n\tsaPrefix = \"projects/-/serviceAccounts/\"\n)\n\n// NewGCPTokenSource creates a token source for GCP access tokens.\n//\n// `client` parameter is optional. If you supply your own client, you have to make\n// sure you set the correct authentication headers yourself. If no client is given,\n// authentication information is looked up from the environment.\n// `defaultSAName` specifies the GCP IAM service accoutn name to use if no\n// dedicated service account is configurred on the key.\nfunc NewGCPTokenSource(ctx context.Context, client *http.Client, scopes []string) (*GCPTokenSource, error) {\n\tservice, err := iam.NewService(ctx, option.WithHTTPClient(client))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to create IAM service client\")\n\t}\n\treturn &GCPTokenSource{service: service, scopes: scopes}, nil\n}\n\n// Token returns an access token for the configured service account and scopes.\n//\n// API: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken\nfunc (g *GCPTokenSource) Token(ctx context.Context, saName, saDelegateName string) (*TokenResponse, error) {\n\tif saName == \"\" {\n\t\treturn nil, fmt.Errorf(\"saName must not be empty\")\n\t}\n\n\tvar delegates []string\n\tif saDelegateName != \"\" {\n\t\t// Impersonation was requested; constructing impersonation chain\n\t\t// [saDelegateName]. For details on impersonation requirements\n\t\t// see: https://docs.cloud.google.com/iam/docs/service-account-impersonation\n\t\tdelegates = []string{saPrefix + saDelegateName}\n\t}\n\treq := iam.GenerateAccessTokenRequest{\n\t\tScope:     g.scopes,\n\t\tDelegates: delegates,\n\t}\n\tresource := saPrefix + saName\n\tslog.DebugContext(ctx, \"Requesting token\", slog.String(\"principal\", resource), slog.Any(\"delegates\", delegates))\n\t// We don't set a 'lifetime' on the request, so we get the default value (3600 sec = 1h).\n\t// This needs to be in sync with the min(cookie-expire,cookie-refresh) duration\n\t// configured on oauth2-proxy.\n\tresp, err := g.service.Projects.ServiceAccounts.\n\t\tGenerateAccessToken(resource, &req).Context(ctx).Do()\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"GenerateAccessToken(..) for %q failed\", resource)\n\t}\n\ttok, err := tokenResponse(resp, g.scopes, time.Now())\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to generate token response from GCP response\")\n\t}\n\treturn tok, nil\n}\n\n// tokenResponse returns a TokenResponse struct given an IAM response object.\nfunc tokenResponse(r *iam.GenerateAccessTokenResponse, scopes []string, now time.Time) (*TokenResponse, error) {\n\ttr := TokenResponse{\n\t\tTokenType:   \"Bearer\",\n\t\tAccessToken: r.AccessToken,\n\t\tScope:       strings.Join(scopes, \" \")}\n\t// calculate ExpiresIn\n\texp, err := time.Parse(time.RFC3339Nano, r.ExpireTime)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to parse expiration time %q\", r.ExpireTime)\n\t}\n\tdiff := now.Sub(exp)\n\ttr.ExpiresIn = int64(math.Abs(diff.Seconds()))\n\treturn &tr, nil\n}\n\nvar workloadServiceAccount = new(atomic.Pointer[string])\n\n// getWorkloadServiceAccount returns a service account email for the pod running\n// token vendor if available. Value is cached, so subsequent calls save time\n// on contacting metadata server.\nfunc getWorkloadServiceAccount(ctx context.Context) (string, error) {\n\tif val := workloadServiceAccount.Load(); val != nil {\n\t\treturn *val, nil\n\t}\n\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\t// Fetch the email for the 'default' service account.\n\t// In Workload Identity, 'default' is the IAM SA mapped to the pod.\n\temail, err := metadata.EmailWithContext(ctx, \"default\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot obtain service account from metadata: %w\", err)\n\t}\n\t// we are going to perform write ONLY if old value is nil, otherwise\n\t// we have the same value stored there anyway due to how metadata\n\t// server works for pods on GKE.\n\tworkloadServiceAccount.CompareAndSwap(nil, &email)\n\treturn email, nil\n}\n"
  },
  {
    "path": "src/go/cmd/token-vendor/tokensource/gcp_test.go",
    "content": "package tokensource\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\tiam \"google.golang.org/api/iamcredentials/v1\"\n)\n\ntype TokenResponseTest struct {\n\tdesc    string\n\treq     iam.GenerateAccessTokenResponse\n\tscopes  []string\n\tnow     time.Time\n\ttr      TokenResponse\n\twantErr bool\n}\n\nfunc TestTokenResponse(t *testing.T) {\n\tnow, _ := time.Parse(time.RFC3339Nano, \"1986-06-30T15:01:23.045123456Z\")\n\tvar cases = []TokenResponseTest{\n\t\t{\n\t\t\tdesc: \"happy path\",\n\t\t\treq: iam.GenerateAccessTokenResponse{\n\t\t\t\tAccessToken: \"abc\",\n\t\t\t\tExpireTime:  \"1986-06-30T15:02:06.045123456Z\"},\n\t\t\tscopes:  []string{\"a\", \"b\"},\n\t\t\tnow:     now,\n\t\t\ttr:      TokenResponse{AccessToken: \"abc\", ExpiresIn: 43, Scope: \"a b\", TokenType: \"Bearer\"},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, test := range cases {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tgot, err := tokenResponse(&test.req, test.scopes, test.now)\n\t\t\tif (test.wantErr && err == nil) || (!test.wantErr) && err != nil {\n\t\t\t\tt.Fatalf(\"tokenResponse(..): got error %v, want %v\", err, test.wantErr)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(got, &test.tr); diff != \"\" {\n\t\t\t\tt.Fatalf(\"tokenResponse(..): got %+v, wanted %+v, diff %v\", got, test.tr, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/generate.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script can be run just like the regular dep tool. It copies the Go\n# code to a shadow repo against dep can operate as usual and copies the\n# resulting Gopkg.toml and Gopkg.lock files to this directory.\n# It then stages the changed dependenies in the bazel WORKSPACE for manual cleanup.\n\nset -e\n\n# K8S release for api, apimachinery and code-generator\nK8S_RELEASE=\"release-1.22\"\n\nCURRENT_DIR=$(pwd)\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\n# We create the shadow repo one dir up because the dep tool falsely tries to\n# truncate the GOPATH we provide after the first /go/ dir it sees.\nSHADOW_REPO=\"${DIR}/../.gopath/src/github.com/googlecloudrobotics/core/src/go\"\n\nexport GOPATH=\"${DIR}/../.gopath\"\nexport GOBIN=\"${GOPATH}/bin\"\n\ngo install k8s.io/code-generator/cmd/{applyconfiguration-gen,defaulter-gen,client-gen,lister-gen,informer-gen,deepcopy-gen}@${K8S_RELEASE}\n\nexport PATH=\"$PATH:$GOPATH/bin\"\nmkdir -p ${SHADOW_REPO}\n\nrm -rf \"${DIR}/pkg/client\"\nrm -rf \"${SHADOW_REPO}/pkg/apis\"\nrm -rf \"${SHADOW_REPO}/pkg/client\"\ncp -r ${DIR}/* ${SHADOW_REPO}\n\nfunction finalize {\n  cp -rT ${SHADOW_REPO}/pkg/client ${DIR}/pkg/client\n  cp -rT ${SHADOW_REPO}/pkg/apis ${DIR}/pkg/apis\n  cd ${CURRENT_DIR}\n\n  # Re-generate BUILD files for generated packages.\n  ${DIR}/../gomod.sh\n}\n\ntrap finalize EXIT\ncd ${SHADOW_REPO}\n\nREPO=github.com/googlecloudrobotics/core/src/go\n\ncat > \"${SHADOW_REPO}/HEADER\" <<EOF\n// Copyright $(date +%Y) The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\nEOF\n\ndirs=\"\"\ngroupversions=\"\"\n\nfor d in ${SHADOW_REPO}/pkg/apis/*/*; do\n  version=$(basename $d)\n  group=$(basename \"$(dirname $d)\")\n  echo \"generating for ${group}/${version}\"\n\n  groupversions=\"${groupversions},${group}/${version}\"\n  dirs=\"${dirs},${REPO}/pkg/apis/${group}/${version}\"\ndone\n\ndirs=\"${dirs:1}\"\ngroupversions=\"${groupversions:1}\"\n\n${GOBIN}/deepcopy-gen \\\n  --go-header-file   \"${SHADOW_REPO}/HEADER\" \\\n  --input-dirs       \"${dirs}\" \\\n  --bounding-dirs    \"${REPO}/pkg/apis\" \\\n  --output-file-base zz_generated.deepcopy\n\n${GOBIN}/client-gen \\\n  --go-header-file \"${SHADOW_REPO}/HEADER\" \\\n  --clientset-name \"versioned\" \\\n  --input-base     \"${REPO}/pkg/apis\" \\\n  --input          \"${groupversions}\" \\\n  --output-package \"${REPO}/pkg/client\" \\\n\n${GOBIN}/lister-gen \\\n  --go-header-file \"${SHADOW_REPO}/HEADER\" \\\n  --input-dirs     \"${dirs}\" \\\n  --output-package \"${REPO}/pkg/client/listers\"\n\n${GOBIN}/informer-gen \\\n  --go-header-file   \"${SHADOW_REPO}/HEADER\" \\\n  --single-directory \\\n  --listers-package  \"${REPO}/pkg/client/listers\" \\\n  --input-dirs       \"${dirs}\" \\\n  --output-package   \"${REPO}/pkg/client/informers\" \\\n  --versioned-clientset-package \"${REPO}/pkg/client/versioned\"\n"
  },
  {
    "path": "src/go/pkg/apis/apps/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"doc.go\",\n        \"register.go\",\n        \"types.go\",\n        \"zz_generated.deepcopy.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/apis/apps/v1alpha1/doc.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +k8s:deepcopy-gen=package\n// +k8s:defaulter-gen=TypeMeta\n// +groupName=apps.cloudrobotics.com\n\npackage v1alpha1\n"
  },
  {
    "path": "src/go/pkg/apis/apps/v1alpha1/register.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar SchemeGroupVersion = schema.GroupVersion{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\"}\n\nvar (\n\t// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.\n\t// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.\n\tSchemeBuilder      runtime.SchemeBuilder\n\tlocalSchemeBuilder = &SchemeBuilder\n\tAddToScheme        = localSchemeBuilder.AddToScheme\n)\n\nfunc init() {\n\t// We only register manually written functions here. The registration of the\n\t// generated functions takes place in the generated files. The separation\n\t// makes the code compile even when the generated files are missing.\n\tlocalSchemeBuilder.Register(addKnownTypes)\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to the given scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&App{},\n\t\t&AppList{},\n\t\t&AppRollout{},\n\t\t&AppRolloutList{},\n\t\t&ChartAssignment{},\n\t\t&ChartAssignmentList{},\n\t\t&ResourceSet{},\n\t\t&ResourceSetList{},\n\t)\n\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&metav1.Status{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/pkg/apis/apps/v1alpha1/types.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage v1alpha1\n\nimport (\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// +genclient\n// +genclient:nonNamespaced\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype ResourceSet struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ResourceSetSpec   `json:\"spec\"`\n\tStatus ResourceSetStatus `json:\"status\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype ResourceSetList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\n\tItems []ResourceSet `json:\"items\"`\n}\n\ntype ResourceSetSpec struct {\n\tResources []ResourceSetSpecGroup `json:\"resources\"`\n}\n\ntype ResourceSetStatus struct {\n\tPhase      ResourceSetPhase         `json:\"phase,omitempty\"`\n\tStartedAt  metav1.Time              `json:\"startedAt,omitempty\"`\n\tFinishedAt metav1.Time              `json:\"finishedAt,omitempty\"`\n\tApplied    []ResourceSetStatusGroup `json:\"applied,omitempty\"`\n\tFailed     []ResourceSetStatusGroup `json:\"failed,omitempty\"`\n}\n\ntype ResourceSetSpecGroup struct {\n\tGroup   string        `json:\"group,omitempty\"` // Is empty for core APIs.\n\tVersion string        `json:\"version\"`\n\tKind    string        `json:\"kind\"`\n\tItems   []ResourceRef `json:\"items\"`\n}\n\ntype ResourceSetStatusGroup struct {\n\tGroup   string           `json:\"group,omitempty\"` // Is empty for core APIs.\n\tVersion string           `json:\"version\"`\n\tKind    string           `json:\"kind\"`\n\tItems   []ResourceStatus `json:\"items\"`\n}\n\ntype ResourceRef struct {\n\tNamespace string `json:\"namespace,omitempty\"`\n\tName      string `json:\"name\"`\n}\n\ntype ResourceStatus struct {\n\tNamespace  string         `json:\"namespace,omitempty\"`\n\tName       string         `json:\"name\"`\n\tAction     ResourceAction `json:\"action\"`\n\tUID        string         `json:\"uid,omitempty\"`\n\tGeneration int64          `json:\"generation,omitempty\"`\n\tError      string         `json:\"error,omitempty\"`\n}\n\ntype ResourceSetPhase string\n\nconst (\n\tResourceSetPhasePending ResourceSetPhase = \"Pending\"\n\tResourceSetPhaseFailed  ResourceSetPhase = \"Failed\"\n\tResourceSetPhaseSettled ResourceSetPhase = \"Settled\"\n)\n\ntype ResourceAction string\n\nconst (\n\tResourceActionNone    ResourceAction = \"None\"\n\tResourceActionCreate  ResourceAction = \"Create\"\n\tResourceActionUpdate  ResourceAction = \"Update\"\n\tResourceActionReplace ResourceAction = \"Replace\"\n)\n\n// +genclient\n// +genclient:nonNamespaced\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype App struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec AppSpec `json:\"spec,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype AppList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\n\tItems []App `json:\"items\"`\n}\n\ntype AppSpec struct {\n\tRepository string        `json:\"repository\"`\n\tVersion    string        `json:\"version\"`\n\tComponents AppComponents `json:\"components\"`\n}\n\ntype AppComponents struct {\n\tCloud AppComponent `json:\"cloud,omitempty\"`\n\tRobot AppComponent `json:\"robot,omitempty\"`\n}\n\ntype AppComponent struct {\n\tName   string `json:\"name,omitempty\"`\n\tInline string `json:\"inline,omitempty\"`\n}\n\n// +genclient\n// +genclient:nonNamespaced\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype AppRollout struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   AppRolloutSpec   `json:\"spec,omitempty\"`\n\tStatus AppRolloutStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype AppRolloutList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\n\tItems []AppRollout `json:\"items\"`\n}\n\ntype AppRolloutSpec struct {\n\tAppName string                `json:\"appName,omitempty\"`\n\tCloud   AppRolloutSpecCloud   `json:\"cloud,omitempty\"`\n\tRobots  []AppRolloutSpecRobot `json:\"robots,omitempty\"`\n}\n\ntype AppRolloutSpecCloud struct {\n\tValues ConfigValues `json:\"values,omitempty\"`\n}\n\ntype AppRolloutSpecRobot struct {\n\tSelector *RobotSelector `json:\"selector,omitempty\"`\n\n\tValues  ConfigValues `json:\"values,omitempty\"`\n\tVersion string       `json:\"version,omitempty\"`\n}\n\ntype RobotSelector struct {\n\t*metav1.LabelSelector\n\n\tAny *bool `json:\"any,omitempty\"`\n}\n\ntype AppRolloutStatus struct {\n\tObservedGeneration int64                 `json:\"observedGeneration,omitempty\"`\n\tConditions         []AppRolloutCondition `json:\"conditions,omitempty\"`\n\tAssignments        int64                 `json:\"assignments\"`\n\tSettledAssignments int64                 `json:\"settledAssignments\"`\n\tReadyAssignments   int64                 `json:\"readyAssignments\"`\n\tFailedAssignments  int64                 `json:\"failedAssignments\"`\n}\n\ntype AppRolloutCondition struct {\n\tType               AppRolloutConditionType `json:\"type\"`\n\tStatus             corev1.ConditionStatus  `json:\"status\"`\n\tLastUpdateTime     metav1.Time             `json:\"lastUpdateTime,omitempty\"`\n\tLastTransitionTime metav1.Time             `json:\"lastTransitionTime,omitempty\"`\n\tMessage            string                  `json:\"message,omitempty\"`\n}\n\ntype AppRolloutConditionType string\n\nconst (\n\tAppRolloutConditionSettled AppRolloutConditionType = \"Settled\"\n\tAppRolloutConditionReady   AppRolloutConditionType = \"Ready\"\n)\n\n// +genclient\n// +genclient:nonNamespaced\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype ChartAssignment struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ChartAssignmentSpec   `json:\"spec,omitempty\"`\n\tStatus ChartAssignmentStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype ChartAssignmentList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\n\tItems []ChartAssignment `json:\"items\"`\n}\n\ntype ChartAssignmentSpec struct {\n\tClusterName   string        `json:\"clusterName\"`\n\tNamespaceName string        `json:\"namespaceName\"`\n\tChart         AssignedChart `json:\"chart\"`\n}\n\ntype AssignedChart struct {\n\tRepository string       `json:\"repository,omitempty\"`\n\tName       string       `json:\"name,omitempty\"`\n\tVersion    string       `json:\"version,omitempty\"`\n\tInline     string       `json:\"inline,omitempty\"`\n\tValues     ConfigValues `json:\"values,omitempty\"`\n}\n\ntype ConfigValues map[string]interface{}\n\n// DeepCopy is an explicit override since the deepcopy generator cannot\n// deal with empty interfaces.\nfunc (in ConfigValues) DeepCopy() ConfigValues {\n\treturn runtime.DeepCopyJSON(in)\n}\n\ntype ChartAssignmentStatus struct {\n\tObservedGeneration int64                      `json:\"observedGeneration,omitempty\"`\n\tPhase              ChartAssignmentPhase       `json:\"phase,omitempty\"`\n\tConditions         []ChartAssignmentCondition `json:\"conditions,omitempty\"`\n}\n\ntype ChartAssignmentPhase string\n\nconst (\n\t// Accepted is set once the controller has observed the CA and started\n\t// taking action.\n\tChartAssignmentPhaseAccepted ChartAssignmentPhase = \"Accepted\"\n\t// LoadingChart marks the begin of parsing the chart and its metadata.\n\tChartAssignmentPhaseLoadingChart ChartAssignmentPhase = \"LoadingChart\"\n\t// TODO(ensonic): Installing is only used with 'helm'.\n\tChartAssignmentPhaseInstalling ChartAssignmentPhase = \"Installing\"\n\t// TODO(ensonic): Updating is only used with 'synk'.\n\tChartAssignmentPhaseUpdating ChartAssignmentPhase = \"Updating\"\n\t// Deleting is set when starting deletion to avoid flipping failure status\n\t// during turndown.\n\tChartAssignmentPhaseDeleting ChartAssignmentPhase = \"Deleting\"\n\t// Settled status signals that all resources from the chart have been applied.\n\tChartAssignmentPhaseSettled ChartAssignmentPhase = \"Settled\"\n\t// Deleted is set when the deletion phase finished.\n\tChartAssignmentPhaseDeleted ChartAssignmentPhase = \"Deleted\"\n\t// Failed indicates that an error occured. The 'err' fields will give extra\n\t// information and 'retry' will indicate if the controller is trying to\n\t// recover from the error.\n\tChartAssignmentPhaseFailed ChartAssignmentPhase = \"Failed\"\n\t// Ready status is set when all pods are running.\n\t// TODO(ensonic): check other resource readyness too?\n\tChartAssignmentPhaseReady ChartAssignmentPhase = \"Ready\"\n)\n\ntype ChartAssignmentCondition struct {\n\tType               ChartAssignmentConditionType `json:\"type\"`\n\tStatus             corev1.ConditionStatus       `json:\"status\"`\n\tLastUpdateTime     metav1.Time                  `json:\"lastUpdateTime,omitempty\"`\n\tLastTransitionTime metav1.Time                  `json:\"lastTransitionTime,omitempty\"`\n\tMessage            string                       `json:\"message,omitempty\"`\n}\n\ntype ChartAssignmentConditionType string\n\nconst (\n\tChartAssignmentConditionSettled ChartAssignmentConditionType = \"Settled\"\n\tChartAssignmentConditionReady   ChartAssignmentConditionType = \"Ready\"\n)\n"
  },
  {
    "path": "src/go/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *App) DeepCopyInto(out *App) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tout.Spec = in.Spec\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new App.\nfunc (in *App) DeepCopy() *App {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(App)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *App) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppComponent) DeepCopyInto(out *AppComponent) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppComponent.\nfunc (in *AppComponent) DeepCopy() *AppComponent {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppComponent)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppComponents) DeepCopyInto(out *AppComponents) {\n\t*out = *in\n\tout.Cloud = in.Cloud\n\tout.Robot = in.Robot\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppComponents.\nfunc (in *AppComponents) DeepCopy() *AppComponents {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppComponents)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppList) DeepCopyInto(out *AppList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]App, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppList.\nfunc (in *AppList) DeepCopy() *AppList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *AppList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRollout) DeepCopyInto(out *AppRollout) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRollout.\nfunc (in *AppRollout) DeepCopy() *AppRollout {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRollout)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *AppRollout) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRolloutCondition) DeepCopyInto(out *AppRolloutCondition) {\n\t*out = *in\n\tin.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutCondition.\nfunc (in *AppRolloutCondition) DeepCopy() *AppRolloutCondition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRolloutCondition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRolloutList) DeepCopyInto(out *AppRolloutList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]AppRollout, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutList.\nfunc (in *AppRolloutList) DeepCopy() *AppRolloutList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRolloutList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *AppRolloutList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRolloutSpec) DeepCopyInto(out *AppRolloutSpec) {\n\t*out = *in\n\tin.Cloud.DeepCopyInto(&out.Cloud)\n\tif in.Robots != nil {\n\t\tin, out := &in.Robots, &out.Robots\n\t\t*out = make([]AppRolloutSpecRobot, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutSpec.\nfunc (in *AppRolloutSpec) DeepCopy() *AppRolloutSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRolloutSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRolloutSpecCloud) DeepCopyInto(out *AppRolloutSpecCloud) {\n\t*out = *in\n\tout.Values = in.Values.DeepCopy()\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutSpecCloud.\nfunc (in *AppRolloutSpecCloud) DeepCopy() *AppRolloutSpecCloud {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRolloutSpecCloud)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRolloutSpecRobot) DeepCopyInto(out *AppRolloutSpecRobot) {\n\t*out = *in\n\tif in.Selector != nil {\n\t\tin, out := &in.Selector, &out.Selector\n\t\t*out = new(RobotSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tout.Values = in.Values.DeepCopy()\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutSpecRobot.\nfunc (in *AppRolloutSpecRobot) DeepCopy() *AppRolloutSpecRobot {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRolloutSpecRobot)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppRolloutStatus) DeepCopyInto(out *AppRolloutStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]AppRolloutCondition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppRolloutStatus.\nfunc (in *AppRolloutStatus) DeepCopy() *AppRolloutStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppRolloutStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AppSpec) DeepCopyInto(out *AppSpec) {\n\t*out = *in\n\tout.Components = in.Components\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppSpec.\nfunc (in *AppSpec) DeepCopy() *AppSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AppSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AssignedChart) DeepCopyInto(out *AssignedChart) {\n\t*out = *in\n\tout.Values = in.Values.DeepCopy()\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AssignedChart.\nfunc (in *AssignedChart) DeepCopy() *AssignedChart {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AssignedChart)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChartAssignment) DeepCopyInto(out *ChartAssignment) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChartAssignment.\nfunc (in *ChartAssignment) DeepCopy() *ChartAssignment {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChartAssignment)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ChartAssignment) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChartAssignmentCondition) DeepCopyInto(out *ChartAssignmentCondition) {\n\t*out = *in\n\tin.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChartAssignmentCondition.\nfunc (in *ChartAssignmentCondition) DeepCopy() *ChartAssignmentCondition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChartAssignmentCondition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChartAssignmentList) DeepCopyInto(out *ChartAssignmentList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ChartAssignment, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChartAssignmentList.\nfunc (in *ChartAssignmentList) DeepCopy() *ChartAssignmentList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChartAssignmentList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ChartAssignmentList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChartAssignmentSpec) DeepCopyInto(out *ChartAssignmentSpec) {\n\t*out = *in\n\tin.Chart.DeepCopyInto(&out.Chart)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChartAssignmentSpec.\nfunc (in *ChartAssignmentSpec) DeepCopy() *ChartAssignmentSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChartAssignmentSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChartAssignmentStatus) DeepCopyInto(out *ChartAssignmentStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]ChartAssignmentCondition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChartAssignmentStatus.\nfunc (in *ChartAssignmentStatus) DeepCopy() *ChartAssignmentStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChartAssignmentStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in ConfigValues) DeepCopyInto(out *ConfigValues) {\n\t{\n\t\tin := &in\n\t\t*out = in.DeepCopy()\n\t\treturn\n\t}\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceRef) DeepCopyInto(out *ResourceRef) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef.\nfunc (in *ResourceRef) DeepCopy() *ResourceRef {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceRef)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceSet) DeepCopyInto(out *ResourceSet) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSet.\nfunc (in *ResourceSet) DeepCopy() *ResourceSet {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceSet)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ResourceSet) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceSetList) DeepCopyInto(out *ResourceSetList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ResourceSet, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSetList.\nfunc (in *ResourceSetList) DeepCopy() *ResourceSetList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceSetList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ResourceSetList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceSetSpec) DeepCopyInto(out *ResourceSetSpec) {\n\t*out = *in\n\tif in.Resources != nil {\n\t\tin, out := &in.Resources, &out.Resources\n\t\t*out = make([]ResourceSetSpecGroup, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSetSpec.\nfunc (in *ResourceSetSpec) DeepCopy() *ResourceSetSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceSetSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceSetSpecGroup) DeepCopyInto(out *ResourceSetSpecGroup) {\n\t*out = *in\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ResourceRef, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSetSpecGroup.\nfunc (in *ResourceSetSpecGroup) DeepCopy() *ResourceSetSpecGroup {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceSetSpecGroup)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceSetStatus) DeepCopyInto(out *ResourceSetStatus) {\n\t*out = *in\n\tin.StartedAt.DeepCopyInto(&out.StartedAt)\n\tin.FinishedAt.DeepCopyInto(&out.FinishedAt)\n\tif in.Applied != nil {\n\t\tin, out := &in.Applied, &out.Applied\n\t\t*out = make([]ResourceSetStatusGroup, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.Failed != nil {\n\t\tin, out := &in.Failed, &out.Failed\n\t\t*out = make([]ResourceSetStatusGroup, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSetStatus.\nfunc (in *ResourceSetStatus) DeepCopy() *ResourceSetStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceSetStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceSetStatusGroup) DeepCopyInto(out *ResourceSetStatusGroup) {\n\t*out = *in\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ResourceStatus, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSetStatusGroup.\nfunc (in *ResourceSetStatusGroup) DeepCopy() *ResourceSetStatusGroup {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceSetStatusGroup)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus.\nfunc (in *ResourceStatus) DeepCopy() *ResourceStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotSelector) DeepCopyInto(out *RobotSelector) {\n\t*out = *in\n\tif in.LabelSelector != nil {\n\t\tin, out := &in.LabelSelector, &out.LabelSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Any != nil {\n\t\tin, out := &in.Any, &out.Any\n\t\t*out = new(bool)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotSelector.\nfunc (in *RobotSelector) DeepCopy() *RobotSelector {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotSelector)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "src/go/pkg/apis/registry/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"doc.go\",\n        \"register.go\",\n        \"types.go\",\n        \"zz_generated.deepcopy.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/apis/registry/v1alpha1/doc.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// +k8s:deepcopy-gen=package\n// +k8s:defaulter-gen=TypeMeta\n// +groupName=registry.cloudrobotics.com\n\npackage v1alpha1\n"
  },
  {
    "path": "src/go/pkg/apis/registry/v1alpha1/register.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar SchemeGroupVersion = schema.GroupVersion{Group: \"registry.cloudrobotics.com\", Version: \"v1alpha1\"}\n\nvar (\n\tSchemeBuilder      runtime.SchemeBuilder\n\tlocalSchemeBuilder = &SchemeBuilder\n\tAddToScheme        = localSchemeBuilder.AddToScheme\n)\n\nfunc init() {\n\tlocalSchemeBuilder.Register(addKnownTypes)\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to the given scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Robot{},\n\t\t&RobotList{},\n\t)\n\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&metav1.Status{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/pkg/apis/registry/v1alpha1/types.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype Robot struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   RobotSpec   `json:\"spec,omitempty\"`\n\tStatus RobotStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\ntype RobotList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\n\tItems []Robot `json:\"items\"`\n}\n\ntype RobotSpec struct {\n\tType    string `json:\"type,omitempty\"`\n\tProject string `json:\"project,omitempty\"`\n}\n\ntype RobotStatus struct {\n\tCloud         RobotStatusCloud   `json:\"cloud,omitempty\"`\n\tRobot         RobotStatusRobot   `json:\"robot,omitempty\"`\n\tConfiguration RobotConfiguration `json:\"configuration,omitempty\"`\n}\n\ntype RobotStatusCloud struct {\n}\n\ntype RobotStatusRobot struct {\n\tInfo map[string]string `json:\"info\",omitempty`\n\n\tUpdateTime                 metav1.Time `json:\"updateTime,omitempty\"`\n\tState                      RobotState  `json:\"state,omitempty\"`\n\tLastStateChange            metav1.Time `json:\"lastStateChangeTime,omitempty\"`\n\tBatteryPercentage          float64     `json:\"batteryPercentage,omitempty\"`\n\tEmergencyStopButtonPressed bool        `json:\"emergencyStopButtonPressed,omitempty\"`\n}\n\ntype RobotConfiguration struct {\n\tTrolleyAttached bool `json:\"trolleyAttached,omitempty\"`\n}\n\ntype RobotState string\n\nconst (\n\tRobotStateUndefined     RobotState = \"UNDEFINED\"\n\tRobotStateUnavailable   RobotState = \"UNAVAILABLE\"\n\tRobotStateAvailable     RobotState = \"AVAILABLE\"\n\tRobotStateEmergencyStop RobotState = \"EMERGENCY_STOP\"\n\tRobotStateError         RobotState = \"ERROR\"\n)\n"
  },
  {
    "path": "src/go/pkg/apis/registry/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Robot) DeepCopyInto(out *Robot) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tout.Spec = in.Spec\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Robot.\nfunc (in *Robot) DeepCopy() *Robot {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Robot)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Robot) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotConfiguration) DeepCopyInto(out *RobotConfiguration) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotConfiguration.\nfunc (in *RobotConfiguration) DeepCopy() *RobotConfiguration {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotConfiguration)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotList) DeepCopyInto(out *RobotList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Robot, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotList.\nfunc (in *RobotList) DeepCopy() *RobotList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *RobotList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotSpec) DeepCopyInto(out *RobotSpec) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotSpec.\nfunc (in *RobotSpec) DeepCopy() *RobotSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotStatus) DeepCopyInto(out *RobotStatus) {\n\t*out = *in\n\tout.Cloud = in.Cloud\n\tin.Robot.DeepCopyInto(&out.Robot)\n\tout.Configuration = in.Configuration\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotStatus.\nfunc (in *RobotStatus) DeepCopy() *RobotStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotStatusCloud) DeepCopyInto(out *RobotStatusCloud) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotStatusCloud.\nfunc (in *RobotStatusCloud) DeepCopy() *RobotStatusCloud {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotStatusCloud)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RobotStatusRobot) DeepCopyInto(out *RobotStatusRobot) {\n\t*out = *in\n\tif in.Info != nil {\n\t\tin, out := &in.Info, &out.Info\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tin.UpdateTime.DeepCopyInto(&out.UpdateTime)\n\tin.LastStateChange.DeepCopyInto(&out.LastStateChange)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RobotStatusRobot.\nfunc (in *RobotStatusRobot) DeepCopy() *RobotStatusRobot {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RobotStatusRobot)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"factory.go\",\n        \"generic.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/informers/apps:go_default_library\",\n        \"//src/go/pkg/client/informers/internalinterfaces:go_default_library\",\n        \"//src/go/pkg/client/informers/registry:go_default_library\",\n        \"//src/go/pkg/client/versioned:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"interface.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/apps\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/client/informers/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/informers/internalinterfaces:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/interface.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage apps\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/apps/v1alpha1\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1alpha1 provides access to shared informers for resources in V1alpha1.\n\tV1alpha1() v1alpha1.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1alpha1 returns a new v1alpha1.Interface.\nfunc (g *group) V1alpha1() v1alpha1.Interface {\n\treturn v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"app.go\",\n        \"approllout.go\",\n        \"chartassignment.go\",\n        \"interface.go\",\n        \"resourceset.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/apps/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/informers/internalinterfaces:go_default_library\",\n        \"//src/go/pkg/client/listers/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/v1alpha1/app.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\ttime \"time\"\n\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/apps/v1alpha1\"\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// AppInformer provides access to a shared informer and lister for\n// Apps.\ntype AppInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() v1alpha1.AppLister\n}\n\ntype appInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// NewAppInformer constructs a new informer for App type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewAppInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredAppInformer(client, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredAppInformer constructs a new informer for App type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredAppInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().Apps().List(context.TODO(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().Apps().Watch(context.TODO(), options)\n\t\t\t},\n\t\t},\n\t\t&appsv1alpha1.App{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *appInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredAppInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *appInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&appsv1alpha1.App{}, f.defaultInformer)\n}\n\nfunc (f *appInformer) Lister() v1alpha1.AppLister {\n\treturn v1alpha1.NewAppLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/v1alpha1/approllout.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\ttime \"time\"\n\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/apps/v1alpha1\"\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// AppRolloutInformer provides access to a shared informer and lister for\n// AppRollouts.\ntype AppRolloutInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() v1alpha1.AppRolloutLister\n}\n\ntype appRolloutInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// NewAppRolloutInformer constructs a new informer for AppRollout type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewAppRolloutInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredAppRolloutInformer(client, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredAppRolloutInformer constructs a new informer for AppRollout type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredAppRolloutInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().AppRollouts().List(context.TODO(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().AppRollouts().Watch(context.TODO(), options)\n\t\t\t},\n\t\t},\n\t\t&appsv1alpha1.AppRollout{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *appRolloutInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredAppRolloutInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *appRolloutInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&appsv1alpha1.AppRollout{}, f.defaultInformer)\n}\n\nfunc (f *appRolloutInformer) Lister() v1alpha1.AppRolloutLister {\n\treturn v1alpha1.NewAppRolloutLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/v1alpha1/chartassignment.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\ttime \"time\"\n\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/apps/v1alpha1\"\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ChartAssignmentInformer provides access to a shared informer and lister for\n// ChartAssignments.\ntype ChartAssignmentInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() v1alpha1.ChartAssignmentLister\n}\n\ntype chartAssignmentInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// NewChartAssignmentInformer constructs a new informer for ChartAssignment type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewChartAssignmentInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredChartAssignmentInformer(client, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredChartAssignmentInformer constructs a new informer for ChartAssignment type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredChartAssignmentInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().ChartAssignments().List(context.TODO(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().ChartAssignments().Watch(context.TODO(), options)\n\t\t\t},\n\t\t},\n\t\t&appsv1alpha1.ChartAssignment{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *chartAssignmentInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredChartAssignmentInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *chartAssignmentInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&appsv1alpha1.ChartAssignment{}, f.defaultInformer)\n}\n\nfunc (f *chartAssignmentInformer) Lister() v1alpha1.ChartAssignmentLister {\n\treturn v1alpha1.NewChartAssignmentLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/v1alpha1/interface.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Apps returns a AppInformer.\n\tApps() AppInformer\n\t// AppRollouts returns a AppRolloutInformer.\n\tAppRollouts() AppRolloutInformer\n\t// ChartAssignments returns a ChartAssignmentInformer.\n\tChartAssignments() ChartAssignmentInformer\n\t// ResourceSets returns a ResourceSetInformer.\n\tResourceSets() ResourceSetInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Apps returns a AppInformer.\nfunc (v *version) Apps() AppInformer {\n\treturn &appInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}\n}\n\n// AppRollouts returns a AppRolloutInformer.\nfunc (v *version) AppRollouts() AppRolloutInformer {\n\treturn &appRolloutInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}\n}\n\n// ChartAssignments returns a ChartAssignmentInformer.\nfunc (v *version) ChartAssignments() ChartAssignmentInformer {\n\treturn &chartAssignmentInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}\n}\n\n// ResourceSets returns a ResourceSetInformer.\nfunc (v *version) ResourceSets() ResourceSetInformer {\n\treturn &resourceSetInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/apps/v1alpha1/resourceset.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\ttime \"time\"\n\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/apps/v1alpha1\"\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ResourceSetInformer provides access to a shared informer and lister for\n// ResourceSets.\ntype ResourceSetInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() v1alpha1.ResourceSetLister\n}\n\ntype resourceSetInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// NewResourceSetInformer constructs a new informer for ResourceSet type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewResourceSetInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredResourceSetInformer(client, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredResourceSetInformer constructs a new informer for ResourceSet type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredResourceSetInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().ResourceSets().List(context.TODO(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.AppsV1alpha1().ResourceSets().Watch(context.TODO(), options)\n\t\t\t},\n\t\t},\n\t\t&appsv1alpha1.ResourceSet{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *resourceSetInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredResourceSetInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *resourceSetInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&appsv1alpha1.ResourceSet{}, f.defaultInformer)\n}\n\nfunc (f *resourceSetInformer) Lister() v1alpha1.ResourceSetLister {\n\treturn v1alpha1.NewResourceSetLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/factory.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage informers\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\ttime \"time\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/apps\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tregistry \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/registry\"\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// SharedInformerOption defines the functional option type for SharedInformerFactory.\ntype SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory\n\ntype sharedInformerFactory struct {\n\tclient           versioned.Interface\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tlock             sync.Mutex\n\tdefaultResync    time.Duration\n\tcustomResync     map[reflect.Type]time.Duration\n\n\tinformers map[reflect.Type]cache.SharedIndexInformer\n\t// startedInformers is used for tracking which informers have been started.\n\t// This allows Start() to be called multiple times safely.\n\tstartedInformers map[reflect.Type]bool\n}\n\n// WithCustomResyncConfig sets a custom resync period for the specified informer types.\nfunc WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfor k, v := range resyncConfig {\n\t\t\tfactory.customResync[reflect.TypeOf(k)] = v\n\t\t}\n\t\treturn factory\n\t}\n}\n\n// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.\nfunc WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.tweakListOptions = tweakListOptions\n\t\treturn factory\n\t}\n}\n\n// WithNamespace limits the SharedInformerFactory to the specified namespace.\nfunc WithNamespace(namespace string) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.namespace = namespace\n\t\treturn factory\n\t}\n}\n\n// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.\nfunc NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync)\n}\n\n// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.\n// Listers obtained via this SharedInformerFactory will be subject to the same filters\n// as specified here.\n// Deprecated: Please use NewSharedInformerFactoryWithOptions instead\nfunc NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))\n}\n\n// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.\nfunc NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {\n\tfactory := &sharedInformerFactory{\n\t\tclient:           client,\n\t\tnamespace:        v1.NamespaceAll,\n\t\tdefaultResync:    defaultResync,\n\t\tinformers:        make(map[reflect.Type]cache.SharedIndexInformer),\n\t\tstartedInformers: make(map[reflect.Type]bool),\n\t\tcustomResync:     make(map[reflect.Type]time.Duration),\n\t}\n\n\t// Apply all options\n\tfor _, opt := range options {\n\t\tfactory = opt(factory)\n\t}\n\n\treturn factory\n}\n\n// Start initializes all requested informers.\nfunc (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tfor informerType, informer := range f.informers {\n\t\tif !f.startedInformers[informerType] {\n\t\t\tgo informer.Run(stopCh)\n\t\t\tf.startedInformers[informerType] = true\n\t\t}\n\t}\n}\n\n// WaitForCacheSync waits for all started informers' cache were synced.\nfunc (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {\n\tinformers := func() map[reflect.Type]cache.SharedIndexInformer {\n\t\tf.lock.Lock()\n\t\tdefer f.lock.Unlock()\n\n\t\tinformers := map[reflect.Type]cache.SharedIndexInformer{}\n\t\tfor informerType, informer := range f.informers {\n\t\t\tif f.startedInformers[informerType] {\n\t\t\t\tinformers[informerType] = informer\n\t\t\t}\n\t\t}\n\t\treturn informers\n\t}()\n\n\tres := map[reflect.Type]bool{}\n\tfor informType, informer := range informers {\n\t\tres[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)\n\t}\n\treturn res\n}\n\n// InternalInformerFor returns the SharedIndexInformer for obj using an internal\n// client.\nfunc (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tinformerType := reflect.TypeOf(obj)\n\tinformer, exists := f.informers[informerType]\n\tif exists {\n\t\treturn informer\n\t}\n\n\tresyncPeriod, exists := f.customResync[informerType]\n\tif !exists {\n\t\tresyncPeriod = f.defaultResync\n\t}\n\n\tinformer = newFunc(f.client, resyncPeriod)\n\tf.informers[informerType] = informer\n\n\treturn informer\n}\n\n// SharedInformerFactory provides shared informers for resources in all known\n// API group versions.\ntype SharedInformerFactory interface {\n\tinternalinterfaces.SharedInformerFactory\n\tForResource(resource schema.GroupVersionResource) (GenericInformer, error)\n\tWaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool\n\n\tApps() apps.Interface\n\tRegistry() registry.Interface\n}\n\nfunc (f *sharedInformerFactory) Apps() apps.Interface {\n\treturn apps.New(f, f.namespace, f.tweakListOptions)\n}\n\nfunc (f *sharedInformerFactory) Registry() registry.Interface {\n\treturn registry.New(f, f.namespace, f.tweakListOptions)\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/generic.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage informers\n\nimport (\n\t\"fmt\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// GenericInformer is type of SharedIndexInformer which will locate and delegate to other\n// sharedInformers based on type\ntype GenericInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() cache.GenericLister\n}\n\ntype genericInformer struct {\n\tinformer cache.SharedIndexInformer\n\tresource schema.GroupResource\n}\n\n// Informer returns the SharedIndexInformer.\nfunc (f *genericInformer) Informer() cache.SharedIndexInformer {\n\treturn f.informer\n}\n\n// Lister returns the GenericLister.\nfunc (f *genericInformer) Lister() cache.GenericLister {\n\treturn cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)\n}\n\n// ForResource gives generic access to a shared informer of the matching type\n// TODO extend this to unknown resources with a client pool\nfunc (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {\n\tswitch resource {\n\t// Group=apps.cloudrobotics.com, Version=v1alpha1\n\tcase v1alpha1.SchemeGroupVersion.WithResource(\"apps\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1alpha1().Apps().Informer()}, nil\n\tcase v1alpha1.SchemeGroupVersion.WithResource(\"approllouts\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1alpha1().AppRollouts().Informer()}, nil\n\tcase v1alpha1.SchemeGroupVersion.WithResource(\"chartassignments\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1alpha1().ChartAssignments().Informer()}, nil\n\tcase v1alpha1.SchemeGroupVersion.WithResource(\"resourcesets\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Apps().V1alpha1().ResourceSets().Informer()}, nil\n\n\t\t// Group=registry.cloudrobotics.com, Version=v1alpha1\n\tcase registryv1alpha1.SchemeGroupVersion.WithResource(\"robots\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Registry().V1alpha1().Robots().Informer()}, nil\n\n\t}\n\n\treturn nil, fmt.Errorf(\"no informer found for %v\", resource)\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/internalinterfaces/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"factory_interfaces.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/client/versioned:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/informers/internalinterfaces/factory_interfaces.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage internalinterfaces\n\nimport (\n\ttime \"time\"\n\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.\ntype NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer\n\n// SharedInformerFactory a small interface to allow for adding an informer without an import cycle\ntype SharedInformerFactory interface {\n\tStart(stopCh <-chan struct{})\n\tInformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer\n}\n\n// TweakListOptionsFunc is a function that transforms a v1.ListOptions.\ntype TweakListOptionsFunc func(*v1.ListOptions)\n"
  },
  {
    "path": "src/go/pkg/client/informers/registry/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"interface.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/registry\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/client/informers/internalinterfaces:go_default_library\",\n        \"//src/go/pkg/client/informers/registry/v1alpha1:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/informers/registry/interface.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage registry\n\nimport (\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/registry/v1alpha1\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1alpha1 provides access to shared informers for resources in V1alpha1.\n\tV1alpha1() v1alpha1.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1alpha1 returns a new v1alpha1.Interface.\nfunc (g *group) V1alpha1() v1alpha1.Interface {\n\treturn v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/registry/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"interface.go\",\n        \"robot.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/registry/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/informers/internalinterfaces:go_default_library\",\n        \"//src/go/pkg/client/listers/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/informers/registry/v1alpha1/interface.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Robots returns a RobotInformer.\n\tRobots() RobotInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Robots returns a RobotInformer.\nfunc (v *version) Robots() RobotInformer {\n\treturn &robotInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "src/go/pkg/client/informers/registry/v1alpha1/robot.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\ttime \"time\"\n\n\tregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tinternalinterfaces \"github.com/googlecloudrobotics/core/src/go/pkg/client/informers/internalinterfaces\"\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/registry/v1alpha1\"\n\tversioned \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// RobotInformer provides access to a shared informer and lister for\n// Robots.\ntype RobotInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() v1alpha1.RobotLister\n}\n\ntype robotInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewRobotInformer constructs a new informer for Robot type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewRobotInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredRobotInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredRobotInformer constructs a new informer for Robot type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredRobotInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.RegistryV1alpha1().Robots(namespace).List(context.TODO(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.RegistryV1alpha1().Robots(namespace).Watch(context.TODO(), options)\n\t\t\t},\n\t\t},\n\t\t&registryv1alpha1.Robot{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *robotInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredRobotInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *robotInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&registryv1alpha1.Robot{}, f.defaultInformer)\n}\n\nfunc (f *robotInformer) Lister() v1alpha1.RobotLister {\n\treturn v1alpha1.NewRobotLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "src/go/pkg/client/listers/apps/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"app.go\",\n        \"approllout.go\",\n        \"chartassignment.go\",\n        \"expansion_generated.go\",\n        \"resourceset.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/apps/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/labels:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/listers/apps/v1alpha1/app.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// AppLister helps list Apps.\n// All objects returned here must be treated as read-only.\ntype AppLister interface {\n\t// List lists all Apps in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.App, err error)\n\t// Get retrieves the App from the index for a given name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*v1alpha1.App, error)\n\tAppListerExpansion\n}\n\n// appLister implements the AppLister interface.\ntype appLister struct {\n\tindexer cache.Indexer\n}\n\n// NewAppLister returns a new AppLister.\nfunc NewAppLister(indexer cache.Indexer) AppLister {\n\treturn &appLister{indexer: indexer}\n}\n\n// List lists all Apps in the indexer.\nfunc (s *appLister) List(selector labels.Selector) (ret []*v1alpha1.App, err error) {\n\terr = cache.ListAll(s.indexer, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.App))\n\t})\n\treturn ret, err\n}\n\n// Get retrieves the App from the index for a given name.\nfunc (s *appLister) Get(name string) (*v1alpha1.App, error) {\n\tobj, exists, err := s.indexer.GetByKey(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\treturn nil, errors.NewNotFound(v1alpha1.Resource(\"app\"), name)\n\t}\n\treturn obj.(*v1alpha1.App), nil\n}\n"
  },
  {
    "path": "src/go/pkg/client/listers/apps/v1alpha1/approllout.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// AppRolloutLister helps list AppRollouts.\n// All objects returned here must be treated as read-only.\ntype AppRolloutLister interface {\n\t// List lists all AppRollouts in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.AppRollout, err error)\n\t// Get retrieves the AppRollout from the index for a given name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*v1alpha1.AppRollout, error)\n\tAppRolloutListerExpansion\n}\n\n// appRolloutLister implements the AppRolloutLister interface.\ntype appRolloutLister struct {\n\tindexer cache.Indexer\n}\n\n// NewAppRolloutLister returns a new AppRolloutLister.\nfunc NewAppRolloutLister(indexer cache.Indexer) AppRolloutLister {\n\treturn &appRolloutLister{indexer: indexer}\n}\n\n// List lists all AppRollouts in the indexer.\nfunc (s *appRolloutLister) List(selector labels.Selector) (ret []*v1alpha1.AppRollout, err error) {\n\terr = cache.ListAll(s.indexer, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.AppRollout))\n\t})\n\treturn ret, err\n}\n\n// Get retrieves the AppRollout from the index for a given name.\nfunc (s *appRolloutLister) Get(name string) (*v1alpha1.AppRollout, error) {\n\tobj, exists, err := s.indexer.GetByKey(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\treturn nil, errors.NewNotFound(v1alpha1.Resource(\"approllout\"), name)\n\t}\n\treturn obj.(*v1alpha1.AppRollout), nil\n}\n"
  },
  {
    "path": "src/go/pkg/client/listers/apps/v1alpha1/chartassignment.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// ChartAssignmentLister helps list ChartAssignments.\n// All objects returned here must be treated as read-only.\ntype ChartAssignmentLister interface {\n\t// List lists all ChartAssignments in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.ChartAssignment, err error)\n\t// Get retrieves the ChartAssignment from the index for a given name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*v1alpha1.ChartAssignment, error)\n\tChartAssignmentListerExpansion\n}\n\n// chartAssignmentLister implements the ChartAssignmentLister interface.\ntype chartAssignmentLister struct {\n\tindexer cache.Indexer\n}\n\n// NewChartAssignmentLister returns a new ChartAssignmentLister.\nfunc NewChartAssignmentLister(indexer cache.Indexer) ChartAssignmentLister {\n\treturn &chartAssignmentLister{indexer: indexer}\n}\n\n// List lists all ChartAssignments in the indexer.\nfunc (s *chartAssignmentLister) List(selector labels.Selector) (ret []*v1alpha1.ChartAssignment, err error) {\n\terr = cache.ListAll(s.indexer, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.ChartAssignment))\n\t})\n\treturn ret, err\n}\n\n// Get retrieves the ChartAssignment from the index for a given name.\nfunc (s *chartAssignmentLister) Get(name string) (*v1alpha1.ChartAssignment, error) {\n\tobj, exists, err := s.indexer.GetByKey(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\treturn nil, errors.NewNotFound(v1alpha1.Resource(\"chartassignment\"), name)\n\t}\n\treturn obj.(*v1alpha1.ChartAssignment), nil\n}\n"
  },
  {
    "path": "src/go/pkg/client/listers/apps/v1alpha1/expansion_generated.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\n// AppListerExpansion allows custom methods to be added to\n// AppLister.\ntype AppListerExpansion interface{}\n\n// AppRolloutListerExpansion allows custom methods to be added to\n// AppRolloutLister.\ntype AppRolloutListerExpansion interface{}\n\n// ChartAssignmentListerExpansion allows custom methods to be added to\n// ChartAssignmentLister.\ntype ChartAssignmentListerExpansion interface{}\n\n// ResourceSetListerExpansion allows custom methods to be added to\n// ResourceSetLister.\ntype ResourceSetListerExpansion interface{}\n"
  },
  {
    "path": "src/go/pkg/client/listers/apps/v1alpha1/resourceset.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// ResourceSetLister helps list ResourceSets.\n// All objects returned here must be treated as read-only.\ntype ResourceSetLister interface {\n\t// List lists all ResourceSets in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.ResourceSet, err error)\n\t// Get retrieves the ResourceSet from the index for a given name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*v1alpha1.ResourceSet, error)\n\tResourceSetListerExpansion\n}\n\n// resourceSetLister implements the ResourceSetLister interface.\ntype resourceSetLister struct {\n\tindexer cache.Indexer\n}\n\n// NewResourceSetLister returns a new ResourceSetLister.\nfunc NewResourceSetLister(indexer cache.Indexer) ResourceSetLister {\n\treturn &resourceSetLister{indexer: indexer}\n}\n\n// List lists all ResourceSets in the indexer.\nfunc (s *resourceSetLister) List(selector labels.Selector) (ret []*v1alpha1.ResourceSet, err error) {\n\terr = cache.ListAll(s.indexer, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.ResourceSet))\n\t})\n\treturn ret, err\n}\n\n// Get retrieves the ResourceSet from the index for a given name.\nfunc (s *resourceSetLister) Get(name string) (*v1alpha1.ResourceSet, error) {\n\tobj, exists, err := s.indexer.GetByKey(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\treturn nil, errors.NewNotFound(v1alpha1.Resource(\"resourceset\"), name)\n\t}\n\treturn obj.(*v1alpha1.ResourceSet), nil\n}\n"
  },
  {
    "path": "src/go/pkg/client/listers/registry/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"expansion_generated.go\",\n        \"robot.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/listers/registry/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/labels:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/listers/registry/v1alpha1/expansion_generated.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\n// RobotListerExpansion allows custom methods to be added to\n// RobotLister.\ntype RobotListerExpansion interface{}\n\n// RobotNamespaceListerExpansion allows custom methods to be added to\n// RobotNamespaceLister.\ntype RobotNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "src/go/pkg/client/listers/registry/v1alpha1/robot.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// RobotLister helps list Robots.\n// All objects returned here must be treated as read-only.\ntype RobotLister interface {\n\t// List lists all Robots in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.Robot, err error)\n\t// Robots returns an object that can list and get Robots.\n\tRobots(namespace string) RobotNamespaceLister\n\tRobotListerExpansion\n}\n\n// robotLister implements the RobotLister interface.\ntype robotLister struct {\n\tindexer cache.Indexer\n}\n\n// NewRobotLister returns a new RobotLister.\nfunc NewRobotLister(indexer cache.Indexer) RobotLister {\n\treturn &robotLister{indexer: indexer}\n}\n\n// List lists all Robots in the indexer.\nfunc (s *robotLister) List(selector labels.Selector) (ret []*v1alpha1.Robot, err error) {\n\terr = cache.ListAll(s.indexer, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.Robot))\n\t})\n\treturn ret, err\n}\n\n// Robots returns an object that can list and get Robots.\nfunc (s *robotLister) Robots(namespace string) RobotNamespaceLister {\n\treturn robotNamespaceLister{indexer: s.indexer, namespace: namespace}\n}\n\n// RobotNamespaceLister helps list and get Robots.\n// All objects returned here must be treated as read-only.\ntype RobotNamespaceLister interface {\n\t// List lists all Robots in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.Robot, err error)\n\t// Get retrieves the Robot from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*v1alpha1.Robot, error)\n\tRobotNamespaceListerExpansion\n}\n\n// robotNamespaceLister implements the RobotNamespaceLister\n// interface.\ntype robotNamespaceLister struct {\n\tindexer   cache.Indexer\n\tnamespace string\n}\n\n// List lists all Robots in the indexer for a given namespace.\nfunc (s robotNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Robot, err error) {\n\terr = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.Robot))\n\t})\n\treturn ret, err\n}\n\n// Get retrieves the Robot from the indexer for a given namespace and name.\nfunc (s robotNamespaceLister) Get(name string) (*v1alpha1.Robot, error) {\n\tobj, exists, err := s.indexer.GetByKey(s.namespace + \"/\" + name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\treturn nil, errors.NewNotFound(v1alpha1.Resource(\"robot\"), name)\n\t}\n\treturn obj.(*v1alpha1.Robot), nil\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"clientset.go\",\n        \"doc.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/client/versioned/typed/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/registry/v1alpha1:go_default_library\",\n        \"@io_k8s_client_go//discovery:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//util/flowcontrol:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/clientset.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage versioned\n\nimport (\n\t\"fmt\"\n\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/apps/v1alpha1\"\n\tregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/registry/v1alpha1\"\n\tdiscovery \"k8s.io/client-go/discovery\"\n\trest \"k8s.io/client-go/rest\"\n\tflowcontrol \"k8s.io/client-go/util/flowcontrol\"\n)\n\ntype Interface interface {\n\tDiscovery() discovery.DiscoveryInterface\n\tAppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface\n\tRegistryV1alpha1() registryv1alpha1.RegistryV1alpha1Interface\n}\n\n// Clientset contains the clients for groups. Each group has exactly one\n// version included in a Clientset.\ntype Clientset struct {\n\t*discovery.DiscoveryClient\n\tappsV1alpha1     *appsv1alpha1.AppsV1alpha1Client\n\tregistryV1alpha1 *registryv1alpha1.RegistryV1alpha1Client\n}\n\n// AppsV1alpha1 retrieves the AppsV1alpha1Client\nfunc (c *Clientset) AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface {\n\treturn c.appsV1alpha1\n}\n\n// RegistryV1alpha1 retrieves the RegistryV1alpha1Client\nfunc (c *Clientset) RegistryV1alpha1() registryv1alpha1.RegistryV1alpha1Interface {\n\treturn c.registryV1alpha1\n}\n\n// Discovery retrieves the DiscoveryClient\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.DiscoveryClient\n}\n\n// NewForConfig creates a new Clientset for the given config.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfig will generate a rate-limiter in configShallowCopy.\nfunc NewForConfig(c *rest.Config) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\tif configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {\n\t\tif configShallowCopy.Burst <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0\")\n\t\t}\n\t\tconfigShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)\n\t}\n\tvar cs Clientset\n\tvar err error\n\tcs.appsV1alpha1, err = appsv1alpha1.NewForConfig(&configShallowCopy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.registryV1alpha1, err = registryv1alpha1.NewForConfig(&configShallowCopy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &cs, nil\n}\n\n// NewForConfigOrDie creates a new Clientset for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *Clientset {\n\tvar cs Clientset\n\tcs.appsV1alpha1 = appsv1alpha1.NewForConfigOrDie(c)\n\tcs.registryV1alpha1 = registryv1alpha1.NewForConfigOrDie(c)\n\n\tcs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)\n\treturn &cs\n}\n\n// New creates a new Clientset for the given RESTClient.\nfunc New(c rest.Interface) *Clientset {\n\tvar cs Clientset\n\tcs.appsV1alpha1 = appsv1alpha1.New(c)\n\tcs.registryV1alpha1 = registryv1alpha1.New(c)\n\n\tcs.DiscoveryClient = discovery.NewDiscoveryClient(c)\n\treturn &cs\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated clientset.\npackage versioned\n"
  },
  {
    "path": "src/go/pkg/client/versioned/fake/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"clientset_generated.go\",\n        \"doc.go\",\n        \"register.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/fake\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/apps/v1alpha1/fake:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/registry/v1alpha1/fake:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//discovery:go_default_library\",\n        \"@io_k8s_client_go//discovery/fake:go_default_library\",\n        \"@io_k8s_client_go//testing:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/fake/clientset_generated.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tclientset \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned\"\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/apps/v1alpha1\"\n\tfakeappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/apps/v1alpha1/fake\"\n\tregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/registry/v1alpha1\"\n\tfakeregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/registry/v1alpha1/fake\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/discovery\"\n\tfakediscovery \"k8s.io/client-go/discovery/fake\"\n\t\"k8s.io/client-go/testing\"\n)\n\n// NewSimpleClientset returns a clientset that will respond with the provided objects.\n// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,\n// without applying any validations and/or defaults. It shouldn't be considered a replacement\n// for a real clientset and is mostly useful in simple unit tests.\nfunc NewSimpleClientset(objects ...runtime.Object) *Clientset {\n\to := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())\n\tfor _, obj := range objects {\n\t\tif err := o.Add(obj); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tcs := &Clientset{tracker: o}\n\tcs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}\n\tcs.AddReactor(\"*\", \"*\", testing.ObjectReaction(o))\n\tcs.AddWatchReactor(\"*\", func(action testing.Action) (handled bool, ret watch.Interface, err error) {\n\t\tgvr := action.GetResource()\n\t\tns := action.GetNamespace()\n\t\twatch, err := o.Watch(gvr, ns)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, watch, nil\n\t})\n\n\treturn cs\n}\n\n// Clientset implements clientset.Interface. Meant to be embedded into a\n// struct to get a default implementation. This makes faking out just the method\n// you want to test easier.\ntype Clientset struct {\n\ttesting.Fake\n\tdiscovery *fakediscovery.FakeDiscovery\n\ttracker   testing.ObjectTracker\n}\n\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\treturn c.discovery\n}\n\nfunc (c *Clientset) Tracker() testing.ObjectTracker {\n\treturn c.tracker\n}\n\nvar (\n\t_ clientset.Interface = &Clientset{}\n\t_ testing.FakeClient  = &Clientset{}\n)\n\n// AppsV1alpha1 retrieves the AppsV1alpha1Client\nfunc (c *Clientset) AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface {\n\treturn &fakeappsv1alpha1.FakeAppsV1alpha1{Fake: &c.Fake}\n}\n\n// RegistryV1alpha1 retrieves the RegistryV1alpha1Client\nfunc (c *Clientset) RegistryV1alpha1() registryv1alpha1.RegistryV1alpha1Interface {\n\treturn &fakeregistryv1alpha1.FakeRegistryV1alpha1{Fake: &c.Fake}\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/fake/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated fake clientset.\npackage fake\n"
  },
  {
    "path": "src/go/pkg/client/versioned/fake/register.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar scheme = runtime.NewScheme()\nvar codecs = serializer.NewCodecFactory(scheme)\n\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\tappsv1alpha1.AddToScheme,\n\tregistryv1alpha1.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(scheme))\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/scheme/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"doc.go\",\n        \"register.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/runtime:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/scheme/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package contains the scheme of the automatically generated clientset.\npackage scheme\n"
  },
  {
    "path": "src/go/pkg/client/versioned/scheme/register.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage scheme\n\nimport (\n\tappsv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tregistryv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar Scheme = runtime.NewScheme()\nvar Codecs = serializer.NewCodecFactory(Scheme)\nvar ParameterCodec = runtime.NewParameterCodec(Scheme)\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\tappsv1alpha1.AddToScheme,\n\tregistryv1alpha1.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(Scheme))\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"app.go\",\n        \"approllout.go\",\n        \"apps_client.go\",\n        \"chartassignment.go\",\n        \"doc.go\",\n        \"generated_expansion.go\",\n        \"resourceset.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/apps/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/scheme:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/app.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tscheme \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\trest \"k8s.io/client-go/rest\"\n)\n\n// AppsGetter has a method to return a AppInterface.\n// A group's client should implement this interface.\ntype AppsGetter interface {\n\tApps() AppInterface\n}\n\n// AppInterface has methods to work with App resources.\ntype AppInterface interface {\n\tCreate(ctx context.Context, app *v1alpha1.App, opts v1.CreateOptions) (*v1alpha1.App, error)\n\tUpdate(ctx context.Context, app *v1alpha1.App, opts v1.UpdateOptions) (*v1alpha1.App, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.App, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*v1alpha1.AppList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.App, err error)\n\tAppExpansion\n}\n\n// apps implements AppInterface\ntype apps struct {\n\tclient rest.Interface\n}\n\n// newApps returns a Apps\nfunc newApps(c *AppsV1alpha1Client) *apps {\n\treturn &apps{\n\t\tclient: c.RESTClient(),\n\t}\n}\n\n// Get takes name of the app, and returns the corresponding app object, and an error if there is any.\nfunc (c *apps) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.App, err error) {\n\tresult = &v1alpha1.App{}\n\terr = c.client.Get().\n\t\tResource(\"apps\").\n\t\tName(name).\n\t\tVersionedParams(&options, scheme.ParameterCodec).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// List takes label and field selectors, and returns the list of Apps that match those selectors.\nfunc (c *apps) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AppList, err error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\tresult = &v1alpha1.AppList{}\n\terr = c.client.Get().\n\t\tResource(\"apps\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Watch returns a watch.Interface that watches the requested apps.\nfunc (c *apps) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\topts.Watch = true\n\treturn c.client.Get().\n\t\tResource(\"apps\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tWatch(ctx)\n}\n\n// Create takes the representation of a app and creates it.  Returns the server's representation of the app, and an error, if there is any.\nfunc (c *apps) Create(ctx context.Context, app *v1alpha1.App, opts v1.CreateOptions) (result *v1alpha1.App, err error) {\n\tresult = &v1alpha1.App{}\n\terr = c.client.Post().\n\t\tResource(\"apps\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(app).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Update takes the representation of a app and updates it. Returns the server's representation of the app, and an error, if there is any.\nfunc (c *apps) Update(ctx context.Context, app *v1alpha1.App, opts v1.UpdateOptions) (result *v1alpha1.App, err error) {\n\tresult = &v1alpha1.App{}\n\terr = c.client.Put().\n\t\tResource(\"apps\").\n\t\tName(app.Name).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(app).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Delete takes name of the app and deletes it. Returns an error if one occurs.\nfunc (c *apps) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tResource(\"apps\").\n\t\tName(name).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *apps) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\tvar timeout time.Duration\n\tif listOpts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second\n\t}\n\treturn c.client.Delete().\n\t\tResource(\"apps\").\n\t\tVersionedParams(&listOpts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// Patch applies the patch and returns the patched app.\nfunc (c *apps) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.App, err error) {\n\tresult = &v1alpha1.App{}\n\terr = c.client.Patch(pt).\n\t\tResource(\"apps\").\n\t\tName(name).\n\t\tSubResource(subresources...).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(data).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/approllout.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tscheme \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\trest \"k8s.io/client-go/rest\"\n)\n\n// AppRolloutsGetter has a method to return a AppRolloutInterface.\n// A group's client should implement this interface.\ntype AppRolloutsGetter interface {\n\tAppRollouts() AppRolloutInterface\n}\n\n// AppRolloutInterface has methods to work with AppRollout resources.\ntype AppRolloutInterface interface {\n\tCreate(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.CreateOptions) (*v1alpha1.AppRollout, error)\n\tUpdate(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.UpdateOptions) (*v1alpha1.AppRollout, error)\n\tUpdateStatus(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.UpdateOptions) (*v1alpha1.AppRollout, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.AppRollout, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*v1alpha1.AppRolloutList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AppRollout, err error)\n\tAppRolloutExpansion\n}\n\n// appRollouts implements AppRolloutInterface\ntype appRollouts struct {\n\tclient rest.Interface\n}\n\n// newAppRollouts returns a AppRollouts\nfunc newAppRollouts(c *AppsV1alpha1Client) *appRollouts {\n\treturn &appRollouts{\n\t\tclient: c.RESTClient(),\n\t}\n}\n\n// Get takes name of the appRollout, and returns the corresponding appRollout object, and an error if there is any.\nfunc (c *appRollouts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.AppRollout, err error) {\n\tresult = &v1alpha1.AppRollout{}\n\terr = c.client.Get().\n\t\tResource(\"approllouts\").\n\t\tName(name).\n\t\tVersionedParams(&options, scheme.ParameterCodec).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// List takes label and field selectors, and returns the list of AppRollouts that match those selectors.\nfunc (c *appRollouts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AppRolloutList, err error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\tresult = &v1alpha1.AppRolloutList{}\n\terr = c.client.Get().\n\t\tResource(\"approllouts\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Watch returns a watch.Interface that watches the requested appRollouts.\nfunc (c *appRollouts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\topts.Watch = true\n\treturn c.client.Get().\n\t\tResource(\"approllouts\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tWatch(ctx)\n}\n\n// Create takes the representation of a appRollout and creates it.  Returns the server's representation of the appRollout, and an error, if there is any.\nfunc (c *appRollouts) Create(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.CreateOptions) (result *v1alpha1.AppRollout, err error) {\n\tresult = &v1alpha1.AppRollout{}\n\terr = c.client.Post().\n\t\tResource(\"approllouts\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(appRollout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Update takes the representation of a appRollout and updates it. Returns the server's representation of the appRollout, and an error, if there is any.\nfunc (c *appRollouts) Update(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.UpdateOptions) (result *v1alpha1.AppRollout, err error) {\n\tresult = &v1alpha1.AppRollout{}\n\terr = c.client.Put().\n\t\tResource(\"approllouts\").\n\t\tName(appRollout.Name).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(appRollout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *appRollouts) UpdateStatus(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.UpdateOptions) (result *v1alpha1.AppRollout, err error) {\n\tresult = &v1alpha1.AppRollout{}\n\terr = c.client.Put().\n\t\tResource(\"approllouts\").\n\t\tName(appRollout.Name).\n\t\tSubResource(\"status\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(appRollout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Delete takes name of the appRollout and deletes it. Returns an error if one occurs.\nfunc (c *appRollouts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tResource(\"approllouts\").\n\t\tName(name).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *appRollouts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\tvar timeout time.Duration\n\tif listOpts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second\n\t}\n\treturn c.client.Delete().\n\t\tResource(\"approllouts\").\n\t\tVersionedParams(&listOpts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// Patch applies the patch and returns the patched appRollout.\nfunc (c *appRollouts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AppRollout, err error) {\n\tresult = &v1alpha1.AppRollout{}\n\terr = c.client.Patch(pt).\n\t\tResource(\"approllouts\").\n\t\tName(name).\n\t\tSubResource(subresources...).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(data).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/apps_client.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype AppsV1alpha1Interface interface {\n\tRESTClient() rest.Interface\n\tAppsGetter\n\tAppRolloutsGetter\n\tChartAssignmentsGetter\n\tResourceSetsGetter\n}\n\n// AppsV1alpha1Client is used to interact with features provided by the apps.cloudrobotics.com group.\ntype AppsV1alpha1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *AppsV1alpha1Client) Apps() AppInterface {\n\treturn newApps(c)\n}\n\nfunc (c *AppsV1alpha1Client) AppRollouts() AppRolloutInterface {\n\treturn newAppRollouts(c)\n}\n\nfunc (c *AppsV1alpha1Client) ChartAssignments() ChartAssignmentInterface {\n\treturn newChartAssignments(c)\n}\n\nfunc (c *AppsV1alpha1Client) ResourceSets() ResourceSetInterface {\n\treturn newResourceSets(c)\n}\n\n// NewForConfig creates a new AppsV1alpha1Client for the given config.\nfunc NewForConfig(c *rest.Config) (*AppsV1alpha1Client, error) {\n\tconfig := *c\n\tif err := setConfigDefaults(&config); err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := rest.RESTClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &AppsV1alpha1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new AppsV1alpha1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *AppsV1alpha1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new AppsV1alpha1Client for the given RESTClient.\nfunc New(c rest.Interface) *AppsV1alpha1Client {\n\treturn &AppsV1alpha1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) error {\n\tgv := v1alpha1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n\n\treturn nil\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *AppsV1alpha1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/chartassignment.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tscheme \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\trest \"k8s.io/client-go/rest\"\n)\n\n// ChartAssignmentsGetter has a method to return a ChartAssignmentInterface.\n// A group's client should implement this interface.\ntype ChartAssignmentsGetter interface {\n\tChartAssignments() ChartAssignmentInterface\n}\n\n// ChartAssignmentInterface has methods to work with ChartAssignment resources.\ntype ChartAssignmentInterface interface {\n\tCreate(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.CreateOptions) (*v1alpha1.ChartAssignment, error)\n\tUpdate(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.UpdateOptions) (*v1alpha1.ChartAssignment, error)\n\tUpdateStatus(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.UpdateOptions) (*v1alpha1.ChartAssignment, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ChartAssignment, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ChartAssignmentList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ChartAssignment, err error)\n\tChartAssignmentExpansion\n}\n\n// chartAssignments implements ChartAssignmentInterface\ntype chartAssignments struct {\n\tclient rest.Interface\n}\n\n// newChartAssignments returns a ChartAssignments\nfunc newChartAssignments(c *AppsV1alpha1Client) *chartAssignments {\n\treturn &chartAssignments{\n\t\tclient: c.RESTClient(),\n\t}\n}\n\n// Get takes name of the chartAssignment, and returns the corresponding chartAssignment object, and an error if there is any.\nfunc (c *chartAssignments) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tresult = &v1alpha1.ChartAssignment{}\n\terr = c.client.Get().\n\t\tResource(\"chartassignments\").\n\t\tName(name).\n\t\tVersionedParams(&options, scheme.ParameterCodec).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// List takes label and field selectors, and returns the list of ChartAssignments that match those selectors.\nfunc (c *chartAssignments) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ChartAssignmentList, err error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\tresult = &v1alpha1.ChartAssignmentList{}\n\terr = c.client.Get().\n\t\tResource(\"chartassignments\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Watch returns a watch.Interface that watches the requested chartAssignments.\nfunc (c *chartAssignments) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\topts.Watch = true\n\treturn c.client.Get().\n\t\tResource(\"chartassignments\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tWatch(ctx)\n}\n\n// Create takes the representation of a chartAssignment and creates it.  Returns the server's representation of the chartAssignment, and an error, if there is any.\nfunc (c *chartAssignments) Create(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.CreateOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tresult = &v1alpha1.ChartAssignment{}\n\terr = c.client.Post().\n\t\tResource(\"chartassignments\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(chartAssignment).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Update takes the representation of a chartAssignment and updates it. Returns the server's representation of the chartAssignment, and an error, if there is any.\nfunc (c *chartAssignments) Update(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.UpdateOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tresult = &v1alpha1.ChartAssignment{}\n\terr = c.client.Put().\n\t\tResource(\"chartassignments\").\n\t\tName(chartAssignment.Name).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(chartAssignment).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *chartAssignments) UpdateStatus(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.UpdateOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tresult = &v1alpha1.ChartAssignment{}\n\terr = c.client.Put().\n\t\tResource(\"chartassignments\").\n\t\tName(chartAssignment.Name).\n\t\tSubResource(\"status\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(chartAssignment).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Delete takes name of the chartAssignment and deletes it. Returns an error if one occurs.\nfunc (c *chartAssignments) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tResource(\"chartassignments\").\n\t\tName(name).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *chartAssignments) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\tvar timeout time.Duration\n\tif listOpts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second\n\t}\n\treturn c.client.Delete().\n\t\tResource(\"chartassignments\").\n\t\tVersionedParams(&listOpts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// Patch applies the patch and returns the patched chartAssignment.\nfunc (c *chartAssignments) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ChartAssignment, err error) {\n\tresult = &v1alpha1.ChartAssignment{}\n\terr = c.client.Patch(pt).\n\t\tResource(\"chartassignments\").\n\t\tName(name).\n\t\tSubResource(subresources...).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(data).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha1\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"doc.go\",\n        \"fake_app.go\",\n        \"fake_approllout.go\",\n        \"fake_apps_client.go\",\n        \"fake_chartassignment.go\",\n        \"fake_resourceset.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/apps/v1alpha1/fake\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/apps/v1alpha1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/labels:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//testing:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_app.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\t\"context\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\n// FakeApps implements AppInterface\ntype FakeApps struct {\n\tFake *FakeAppsV1alpha1\n}\n\nvar appsResource = schema.GroupVersionResource{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"apps\"}\n\nvar appsKind = schema.GroupVersionKind{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Kind: \"App\"}\n\n// Get takes name of the app, and returns the corresponding app object, and an error if there is any.\nfunc (c *FakeApps) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.App, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootGetAction(appsResource, name), &v1alpha1.App{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.App), err\n}\n\n// List takes label and field selectors, and returns the list of Apps that match those selectors.\nfunc (c *FakeApps) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AppList, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootListAction(appsResource, appsKind, opts), &v1alpha1.AppList{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\n\tlabel, _, _ := testing.ExtractFromListOptions(opts)\n\tif label == nil {\n\t\tlabel = labels.Everything()\n\t}\n\tlist := &v1alpha1.AppList{ListMeta: obj.(*v1alpha1.AppList).ListMeta}\n\tfor _, item := range obj.(*v1alpha1.AppList).Items {\n\t\tif label.Matches(labels.Set(item.Labels)) {\n\t\t\tlist.Items = append(list.Items, item)\n\t\t}\n\t}\n\treturn list, err\n}\n\n// Watch returns a watch.Interface that watches the requested apps.\nfunc (c *FakeApps) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\treturn c.Fake.\n\t\tInvokesWatch(testing.NewRootWatchAction(appsResource, opts))\n}\n\n// Create takes the representation of a app and creates it.  Returns the server's representation of the app, and an error, if there is any.\nfunc (c *FakeApps) Create(ctx context.Context, app *v1alpha1.App, opts v1.CreateOptions) (result *v1alpha1.App, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootCreateAction(appsResource, app), &v1alpha1.App{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.App), err\n}\n\n// Update takes the representation of a app and updates it. Returns the server's representation of the app, and an error, if there is any.\nfunc (c *FakeApps) Update(ctx context.Context, app *v1alpha1.App, opts v1.UpdateOptions) (result *v1alpha1.App, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateAction(appsResource, app), &v1alpha1.App{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.App), err\n}\n\n// Delete takes name of the app and deletes it. Returns an error if one occurs.\nfunc (c *FakeApps) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\t_, err := c.Fake.\n\t\tInvokes(testing.NewRootDeleteAction(appsResource, name), &v1alpha1.App{})\n\treturn err\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *FakeApps) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\taction := testing.NewRootDeleteCollectionAction(appsResource, listOpts)\n\n\t_, err := c.Fake.Invokes(action, &v1alpha1.AppList{})\n\treturn err\n}\n\n// Patch applies the patch and returns the patched app.\nfunc (c *FakeApps) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.App, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootPatchSubresourceAction(appsResource, name, pt, data, subresources...), &v1alpha1.App{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.App), err\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_approllout.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\t\"context\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\n// FakeAppRollouts implements AppRolloutInterface\ntype FakeAppRollouts struct {\n\tFake *FakeAppsV1alpha1\n}\n\nvar approlloutsResource = schema.GroupVersionResource{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"approllouts\"}\n\nvar approlloutsKind = schema.GroupVersionKind{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Kind: \"AppRollout\"}\n\n// Get takes name of the appRollout, and returns the corresponding appRollout object, and an error if there is any.\nfunc (c *FakeAppRollouts) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.AppRollout, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootGetAction(approlloutsResource, name), &v1alpha1.AppRollout{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.AppRollout), err\n}\n\n// List takes label and field selectors, and returns the list of AppRollouts that match those selectors.\nfunc (c *FakeAppRollouts) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.AppRolloutList, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootListAction(approlloutsResource, approlloutsKind, opts), &v1alpha1.AppRolloutList{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\n\tlabel, _, _ := testing.ExtractFromListOptions(opts)\n\tif label == nil {\n\t\tlabel = labels.Everything()\n\t}\n\tlist := &v1alpha1.AppRolloutList{ListMeta: obj.(*v1alpha1.AppRolloutList).ListMeta}\n\tfor _, item := range obj.(*v1alpha1.AppRolloutList).Items {\n\t\tif label.Matches(labels.Set(item.Labels)) {\n\t\t\tlist.Items = append(list.Items, item)\n\t\t}\n\t}\n\treturn list, err\n}\n\n// Watch returns a watch.Interface that watches the requested appRollouts.\nfunc (c *FakeAppRollouts) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\treturn c.Fake.\n\t\tInvokesWatch(testing.NewRootWatchAction(approlloutsResource, opts))\n}\n\n// Create takes the representation of a appRollout and creates it.  Returns the server's representation of the appRollout, and an error, if there is any.\nfunc (c *FakeAppRollouts) Create(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.CreateOptions) (result *v1alpha1.AppRollout, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootCreateAction(approlloutsResource, appRollout), &v1alpha1.AppRollout{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.AppRollout), err\n}\n\n// Update takes the representation of a appRollout and updates it. Returns the server's representation of the appRollout, and an error, if there is any.\nfunc (c *FakeAppRollouts) Update(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.UpdateOptions) (result *v1alpha1.AppRollout, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateAction(approlloutsResource, appRollout), &v1alpha1.AppRollout{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.AppRollout), err\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *FakeAppRollouts) UpdateStatus(ctx context.Context, appRollout *v1alpha1.AppRollout, opts v1.UpdateOptions) (*v1alpha1.AppRollout, error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateSubresourceAction(approlloutsResource, \"status\", appRollout), &v1alpha1.AppRollout{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.AppRollout), err\n}\n\n// Delete takes name of the appRollout and deletes it. Returns an error if one occurs.\nfunc (c *FakeAppRollouts) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\t_, err := c.Fake.\n\t\tInvokes(testing.NewRootDeleteAction(approlloutsResource, name), &v1alpha1.AppRollout{})\n\treturn err\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *FakeAppRollouts) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\taction := testing.NewRootDeleteCollectionAction(approlloutsResource, listOpts)\n\n\t_, err := c.Fake.Invokes(action, &v1alpha1.AppRolloutList{})\n\treturn err\n}\n\n// Patch applies the patch and returns the patched appRollout.\nfunc (c *FakeAppRollouts) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.AppRollout, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootPatchSubresourceAction(approlloutsResource, name, pt, data, subresources...), &v1alpha1.AppRollout{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.AppRollout), err\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_apps_client.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/apps/v1alpha1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeAppsV1alpha1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeAppsV1alpha1) Apps() v1alpha1.AppInterface {\n\treturn &FakeApps{c}\n}\n\nfunc (c *FakeAppsV1alpha1) AppRollouts() v1alpha1.AppRolloutInterface {\n\treturn &FakeAppRollouts{c}\n}\n\nfunc (c *FakeAppsV1alpha1) ChartAssignments() v1alpha1.ChartAssignmentInterface {\n\treturn &FakeChartAssignments{c}\n}\n\nfunc (c *FakeAppsV1alpha1) ResourceSets() v1alpha1.ResourceSetInterface {\n\treturn &FakeResourceSets{c}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeAppsV1alpha1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_chartassignment.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\t\"context\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\n// FakeChartAssignments implements ChartAssignmentInterface\ntype FakeChartAssignments struct {\n\tFake *FakeAppsV1alpha1\n}\n\nvar chartassignmentsResource = schema.GroupVersionResource{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"chartassignments\"}\n\nvar chartassignmentsKind = schema.GroupVersionKind{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Kind: \"ChartAssignment\"}\n\n// Get takes name of the chartAssignment, and returns the corresponding chartAssignment object, and an error if there is any.\nfunc (c *FakeChartAssignments) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootGetAction(chartassignmentsResource, name), &v1alpha1.ChartAssignment{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ChartAssignment), err\n}\n\n// List takes label and field selectors, and returns the list of ChartAssignments that match those selectors.\nfunc (c *FakeChartAssignments) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ChartAssignmentList, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootListAction(chartassignmentsResource, chartassignmentsKind, opts), &v1alpha1.ChartAssignmentList{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\n\tlabel, _, _ := testing.ExtractFromListOptions(opts)\n\tif label == nil {\n\t\tlabel = labels.Everything()\n\t}\n\tlist := &v1alpha1.ChartAssignmentList{ListMeta: obj.(*v1alpha1.ChartAssignmentList).ListMeta}\n\tfor _, item := range obj.(*v1alpha1.ChartAssignmentList).Items {\n\t\tif label.Matches(labels.Set(item.Labels)) {\n\t\t\tlist.Items = append(list.Items, item)\n\t\t}\n\t}\n\treturn list, err\n}\n\n// Watch returns a watch.Interface that watches the requested chartAssignments.\nfunc (c *FakeChartAssignments) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\treturn c.Fake.\n\t\tInvokesWatch(testing.NewRootWatchAction(chartassignmentsResource, opts))\n}\n\n// Create takes the representation of a chartAssignment and creates it.  Returns the server's representation of the chartAssignment, and an error, if there is any.\nfunc (c *FakeChartAssignments) Create(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.CreateOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootCreateAction(chartassignmentsResource, chartAssignment), &v1alpha1.ChartAssignment{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ChartAssignment), err\n}\n\n// Update takes the representation of a chartAssignment and updates it. Returns the server's representation of the chartAssignment, and an error, if there is any.\nfunc (c *FakeChartAssignments) Update(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.UpdateOptions) (result *v1alpha1.ChartAssignment, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateAction(chartassignmentsResource, chartAssignment), &v1alpha1.ChartAssignment{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ChartAssignment), err\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *FakeChartAssignments) UpdateStatus(ctx context.Context, chartAssignment *v1alpha1.ChartAssignment, opts v1.UpdateOptions) (*v1alpha1.ChartAssignment, error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateSubresourceAction(chartassignmentsResource, \"status\", chartAssignment), &v1alpha1.ChartAssignment{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ChartAssignment), err\n}\n\n// Delete takes name of the chartAssignment and deletes it. Returns an error if one occurs.\nfunc (c *FakeChartAssignments) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\t_, err := c.Fake.\n\t\tInvokes(testing.NewRootDeleteAction(chartassignmentsResource, name), &v1alpha1.ChartAssignment{})\n\treturn err\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *FakeChartAssignments) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\taction := testing.NewRootDeleteCollectionAction(chartassignmentsResource, listOpts)\n\n\t_, err := c.Fake.Invokes(action, &v1alpha1.ChartAssignmentList{})\n\treturn err\n}\n\n// Patch applies the patch and returns the patched chartAssignment.\nfunc (c *FakeChartAssignments) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ChartAssignment, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootPatchSubresourceAction(chartassignmentsResource, name, pt, data, subresources...), &v1alpha1.ChartAssignment{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ChartAssignment), err\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/fake/fake_resourceset.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\t\"context\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\n// FakeResourceSets implements ResourceSetInterface\ntype FakeResourceSets struct {\n\tFake *FakeAppsV1alpha1\n}\n\nvar resourcesetsResource = schema.GroupVersionResource{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"resourcesets\"}\n\nvar resourcesetsKind = schema.GroupVersionKind{Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Kind: \"ResourceSet\"}\n\n// Get takes name of the resourceSet, and returns the corresponding resourceSet object, and an error if there is any.\nfunc (c *FakeResourceSets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ResourceSet, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootGetAction(resourcesetsResource, name), &v1alpha1.ResourceSet{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ResourceSet), err\n}\n\n// List takes label and field selectors, and returns the list of ResourceSets that match those selectors.\nfunc (c *FakeResourceSets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ResourceSetList, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootListAction(resourcesetsResource, resourcesetsKind, opts), &v1alpha1.ResourceSetList{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\n\tlabel, _, _ := testing.ExtractFromListOptions(opts)\n\tif label == nil {\n\t\tlabel = labels.Everything()\n\t}\n\tlist := &v1alpha1.ResourceSetList{ListMeta: obj.(*v1alpha1.ResourceSetList).ListMeta}\n\tfor _, item := range obj.(*v1alpha1.ResourceSetList).Items {\n\t\tif label.Matches(labels.Set(item.Labels)) {\n\t\t\tlist.Items = append(list.Items, item)\n\t\t}\n\t}\n\treturn list, err\n}\n\n// Watch returns a watch.Interface that watches the requested resourceSets.\nfunc (c *FakeResourceSets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\treturn c.Fake.\n\t\tInvokesWatch(testing.NewRootWatchAction(resourcesetsResource, opts))\n}\n\n// Create takes the representation of a resourceSet and creates it.  Returns the server's representation of the resourceSet, and an error, if there is any.\nfunc (c *FakeResourceSets) Create(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.CreateOptions) (result *v1alpha1.ResourceSet, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootCreateAction(resourcesetsResource, resourceSet), &v1alpha1.ResourceSet{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ResourceSet), err\n}\n\n// Update takes the representation of a resourceSet and updates it. Returns the server's representation of the resourceSet, and an error, if there is any.\nfunc (c *FakeResourceSets) Update(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.UpdateOptions) (result *v1alpha1.ResourceSet, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateAction(resourcesetsResource, resourceSet), &v1alpha1.ResourceSet{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ResourceSet), err\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *FakeResourceSets) UpdateStatus(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.UpdateOptions) (*v1alpha1.ResourceSet, error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootUpdateSubresourceAction(resourcesetsResource, \"status\", resourceSet), &v1alpha1.ResourceSet{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ResourceSet), err\n}\n\n// Delete takes name of the resourceSet and deletes it. Returns an error if one occurs.\nfunc (c *FakeResourceSets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\t_, err := c.Fake.\n\t\tInvokes(testing.NewRootDeleteAction(resourcesetsResource, name), &v1alpha1.ResourceSet{})\n\treturn err\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *FakeResourceSets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\taction := testing.NewRootDeleteCollectionAction(resourcesetsResource, listOpts)\n\n\t_, err := c.Fake.Invokes(action, &v1alpha1.ResourceSetList{})\n\treturn err\n}\n\n// Patch applies the patch and returns the patched resourceSet.\nfunc (c *FakeResourceSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ResourceSet, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewRootPatchSubresourceAction(resourcesetsResource, name, pt, data, subresources...), &v1alpha1.ResourceSet{})\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.ResourceSet), err\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/generated_expansion.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\ntype AppExpansion interface{}\n\ntype AppRolloutExpansion interface{}\n\ntype ChartAssignmentExpansion interface{}\n\ntype ResourceSetExpansion interface{}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/apps/v1alpha1/resourceset.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tscheme \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\trest \"k8s.io/client-go/rest\"\n)\n\n// ResourceSetsGetter has a method to return a ResourceSetInterface.\n// A group's client should implement this interface.\ntype ResourceSetsGetter interface {\n\tResourceSets() ResourceSetInterface\n}\n\n// ResourceSetInterface has methods to work with ResourceSet resources.\ntype ResourceSetInterface interface {\n\tCreate(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.CreateOptions) (*v1alpha1.ResourceSet, error)\n\tUpdate(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.UpdateOptions) (*v1alpha1.ResourceSet, error)\n\tUpdateStatus(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.UpdateOptions) (*v1alpha1.ResourceSet, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ResourceSet, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ResourceSetList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ResourceSet, err error)\n\tResourceSetExpansion\n}\n\n// resourceSets implements ResourceSetInterface\ntype resourceSets struct {\n\tclient rest.Interface\n}\n\n// newResourceSets returns a ResourceSets\nfunc newResourceSets(c *AppsV1alpha1Client) *resourceSets {\n\treturn &resourceSets{\n\t\tclient: c.RESTClient(),\n\t}\n}\n\n// Get takes name of the resourceSet, and returns the corresponding resourceSet object, and an error if there is any.\nfunc (c *resourceSets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ResourceSet, err error) {\n\tresult = &v1alpha1.ResourceSet{}\n\terr = c.client.Get().\n\t\tResource(\"resourcesets\").\n\t\tName(name).\n\t\tVersionedParams(&options, scheme.ParameterCodec).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// List takes label and field selectors, and returns the list of ResourceSets that match those selectors.\nfunc (c *resourceSets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ResourceSetList, err error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\tresult = &v1alpha1.ResourceSetList{}\n\terr = c.client.Get().\n\t\tResource(\"resourcesets\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Watch returns a watch.Interface that watches the requested resourceSets.\nfunc (c *resourceSets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\topts.Watch = true\n\treturn c.client.Get().\n\t\tResource(\"resourcesets\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tWatch(ctx)\n}\n\n// Create takes the representation of a resourceSet and creates it.  Returns the server's representation of the resourceSet, and an error, if there is any.\nfunc (c *resourceSets) Create(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.CreateOptions) (result *v1alpha1.ResourceSet, err error) {\n\tresult = &v1alpha1.ResourceSet{}\n\terr = c.client.Post().\n\t\tResource(\"resourcesets\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(resourceSet).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Update takes the representation of a resourceSet and updates it. Returns the server's representation of the resourceSet, and an error, if there is any.\nfunc (c *resourceSets) Update(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.UpdateOptions) (result *v1alpha1.ResourceSet, err error) {\n\tresult = &v1alpha1.ResourceSet{}\n\terr = c.client.Put().\n\t\tResource(\"resourcesets\").\n\t\tName(resourceSet.Name).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(resourceSet).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *resourceSets) UpdateStatus(ctx context.Context, resourceSet *v1alpha1.ResourceSet, opts v1.UpdateOptions) (result *v1alpha1.ResourceSet, err error) {\n\tresult = &v1alpha1.ResourceSet{}\n\terr = c.client.Put().\n\t\tResource(\"resourcesets\").\n\t\tName(resourceSet.Name).\n\t\tSubResource(\"status\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(resourceSet).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Delete takes name of the resourceSet and deletes it. Returns an error if one occurs.\nfunc (c *resourceSets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tResource(\"resourcesets\").\n\t\tName(name).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *resourceSets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\tvar timeout time.Duration\n\tif listOpts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second\n\t}\n\treturn c.client.Delete().\n\t\tResource(\"resourcesets\").\n\t\tVersionedParams(&listOpts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// Patch applies the patch and returns the patched resourceSet.\nfunc (c *resourceSets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ResourceSet, err error) {\n\tresult = &v1alpha1.ResourceSet{}\n\terr = c.client.Patch(pt).\n\t\tResource(\"resourcesets\").\n\t\tName(name).\n\t\tSubResource(subresources...).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(data).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"doc.go\",\n        \"generated_expansion.go\",\n        \"registry_client.go\",\n        \"robot.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/registry/v1alpha1\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/scheme:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha1\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/fake/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"doc.go\",\n        \"fake_registry_client.go\",\n        \"fake_robot.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/registry/v1alpha1/fake\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"//src/go/pkg/client/versioned/typed/registry/v1alpha1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/labels:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//testing:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/fake/doc.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/fake/fake_registry_client.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/typed/registry/v1alpha1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeRegistryV1alpha1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeRegistryV1alpha1) Robots(namespace string) v1alpha1.RobotInterface {\n\treturn &FakeRobots{c, namespace}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeRegistryV1alpha1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/fake/fake_robot.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\t\"context\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\n// FakeRobots implements RobotInterface\ntype FakeRobots struct {\n\tFake *FakeRegistryV1alpha1\n\tns   string\n}\n\nvar robotsResource = schema.GroupVersionResource{Group: \"registry.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"robots\"}\n\nvar robotsKind = schema.GroupVersionKind{Group: \"registry.cloudrobotics.com\", Version: \"v1alpha1\", Kind: \"Robot\"}\n\n// Get takes name of the robot, and returns the corresponding robot object, and an error if there is any.\nfunc (c *FakeRobots) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Robot, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewGetAction(robotsResource, c.ns, name), &v1alpha1.Robot{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.Robot), err\n}\n\n// List takes label and field selectors, and returns the list of Robots that match those selectors.\nfunc (c *FakeRobots) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RobotList, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewListAction(robotsResource, robotsKind, c.ns, opts), &v1alpha1.RobotList{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\n\tlabel, _, _ := testing.ExtractFromListOptions(opts)\n\tif label == nil {\n\t\tlabel = labels.Everything()\n\t}\n\tlist := &v1alpha1.RobotList{ListMeta: obj.(*v1alpha1.RobotList).ListMeta}\n\tfor _, item := range obj.(*v1alpha1.RobotList).Items {\n\t\tif label.Matches(labels.Set(item.Labels)) {\n\t\t\tlist.Items = append(list.Items, item)\n\t\t}\n\t}\n\treturn list, err\n}\n\n// Watch returns a watch.Interface that watches the requested robots.\nfunc (c *FakeRobots) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\treturn c.Fake.\n\t\tInvokesWatch(testing.NewWatchAction(robotsResource, c.ns, opts))\n\n}\n\n// Create takes the representation of a robot and creates it.  Returns the server's representation of the robot, and an error, if there is any.\nfunc (c *FakeRobots) Create(ctx context.Context, robot *v1alpha1.Robot, opts v1.CreateOptions) (result *v1alpha1.Robot, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewCreateAction(robotsResource, c.ns, robot), &v1alpha1.Robot{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.Robot), err\n}\n\n// Update takes the representation of a robot and updates it. Returns the server's representation of the robot, and an error, if there is any.\nfunc (c *FakeRobots) Update(ctx context.Context, robot *v1alpha1.Robot, opts v1.UpdateOptions) (result *v1alpha1.Robot, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewUpdateAction(robotsResource, c.ns, robot), &v1alpha1.Robot{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.Robot), err\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *FakeRobots) UpdateStatus(ctx context.Context, robot *v1alpha1.Robot, opts v1.UpdateOptions) (*v1alpha1.Robot, error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewUpdateSubresourceAction(robotsResource, \"status\", c.ns, robot), &v1alpha1.Robot{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.Robot), err\n}\n\n// Delete takes name of the robot and deletes it. Returns an error if one occurs.\nfunc (c *FakeRobots) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\t_, err := c.Fake.\n\t\tInvokes(testing.NewDeleteAction(robotsResource, c.ns, name), &v1alpha1.Robot{})\n\n\treturn err\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *FakeRobots) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\taction := testing.NewDeleteCollectionAction(robotsResource, c.ns, listOpts)\n\n\t_, err := c.Fake.Invokes(action, &v1alpha1.RobotList{})\n\treturn err\n}\n\n// Patch applies the patch and returns the patched robot.\nfunc (c *FakeRobots) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Robot, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewPatchSubresourceAction(robotsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Robot{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.Robot), err\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/generated_expansion.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\ntype RobotExpansion interface{}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/registry_client.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype RegistryV1alpha1Interface interface {\n\tRESTClient() rest.Interface\n\tRobotsGetter\n}\n\n// RegistryV1alpha1Client is used to interact with features provided by the registry.cloudrobotics.com group.\ntype RegistryV1alpha1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *RegistryV1alpha1Client) Robots(namespace string) RobotInterface {\n\treturn newRobots(c, namespace)\n}\n\n// NewForConfig creates a new RegistryV1alpha1Client for the given config.\nfunc NewForConfig(c *rest.Config) (*RegistryV1alpha1Client, error) {\n\tconfig := *c\n\tif err := setConfigDefaults(&config); err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := rest.RESTClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &RegistryV1alpha1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new RegistryV1alpha1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *RegistryV1alpha1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new RegistryV1alpha1Client for the given RESTClient.\nfunc New(c rest.Interface) *RegistryV1alpha1Client {\n\treturn &RegistryV1alpha1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) error {\n\tgv := v1alpha1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n\n\treturn nil\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *RegistryV1alpha1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "src/go/pkg/client/versioned/typed/registry/v1alpha1/robot.go",
    "content": "// Copyright 2026 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tv1alpha1 \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tscheme \"github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\trest \"k8s.io/client-go/rest\"\n)\n\n// RobotsGetter has a method to return a RobotInterface.\n// A group's client should implement this interface.\ntype RobotsGetter interface {\n\tRobots(namespace string) RobotInterface\n}\n\n// RobotInterface has methods to work with Robot resources.\ntype RobotInterface interface {\n\tCreate(ctx context.Context, robot *v1alpha1.Robot, opts v1.CreateOptions) (*v1alpha1.Robot, error)\n\tUpdate(ctx context.Context, robot *v1alpha1.Robot, opts v1.UpdateOptions) (*v1alpha1.Robot, error)\n\tUpdateStatus(ctx context.Context, robot *v1alpha1.Robot, opts v1.UpdateOptions) (*v1alpha1.Robot, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Robot, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*v1alpha1.RobotList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Robot, err error)\n\tRobotExpansion\n}\n\n// robots implements RobotInterface\ntype robots struct {\n\tclient rest.Interface\n\tns     string\n}\n\n// newRobots returns a Robots\nfunc newRobots(c *RegistryV1alpha1Client, namespace string) *robots {\n\treturn &robots{\n\t\tclient: c.RESTClient(),\n\t\tns:     namespace,\n\t}\n}\n\n// Get takes name of the robot, and returns the corresponding robot object, and an error if there is any.\nfunc (c *robots) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Robot, err error) {\n\tresult = &v1alpha1.Robot{}\n\terr = c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tName(name).\n\t\tVersionedParams(&options, scheme.ParameterCodec).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// List takes label and field selectors, and returns the list of Robots that match those selectors.\nfunc (c *robots) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.RobotList, err error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\tresult = &v1alpha1.RobotList{}\n\terr = c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Watch returns a watch.Interface that watches the requested robots.\nfunc (c *robots) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\topts.Watch = true\n\treturn c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tWatch(ctx)\n}\n\n// Create takes the representation of a robot and creates it.  Returns the server's representation of the robot, and an error, if there is any.\nfunc (c *robots) Create(ctx context.Context, robot *v1alpha1.Robot, opts v1.CreateOptions) (result *v1alpha1.Robot, err error) {\n\tresult = &v1alpha1.Robot{}\n\terr = c.client.Post().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(robot).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Update takes the representation of a robot and updates it. Returns the server's representation of the robot, and an error, if there is any.\nfunc (c *robots) Update(ctx context.Context, robot *v1alpha1.Robot, opts v1.UpdateOptions) (result *v1alpha1.Robot, err error) {\n\tresult = &v1alpha1.Robot{}\n\terr = c.client.Put().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tName(robot.Name).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(robot).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *robots) UpdateStatus(ctx context.Context, robot *v1alpha1.Robot, opts v1.UpdateOptions) (result *v1alpha1.Robot, err error) {\n\tresult = &v1alpha1.Robot{}\n\terr = c.client.Put().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tName(robot.Name).\n\t\tSubResource(\"status\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(robot).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Delete takes name of the robot and deletes it. Returns an error if one occurs.\nfunc (c *robots) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tName(name).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *robots) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\tvar timeout time.Duration\n\tif listOpts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second\n\t}\n\treturn c.client.Delete().\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tVersionedParams(&listOpts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// Patch applies the patch and returns the patched robot.\nfunc (c *robots) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Robot, err error) {\n\tresult = &v1alpha1.Robot{}\n\terr = c.client.Patch(pt).\n\t\tNamespace(c.ns).\n\t\tResource(\"robots\").\n\t\tName(name).\n\t\tSubResource(subresources...).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(data).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n"
  },
  {
    "path": "src/go/pkg/configutil/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"config_reader.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/configutil\",\n    deps = [\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_google_cloud_go_storage//:go_default_library\",\n        \"@org_golang_google_api//option:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\"config_reader_test.go\"],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n)\n"
  },
  {
    "path": "src/go/pkg/configutil/config_reader.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage configutil\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"io\"\n\t\"log/slog\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"cloud.google.com/go/storage\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"google.golang.org/api/option\"\n)\n\n// Unescapes a bash string. Only supports the following three patterns:\n//\n//\tHello\\ \\w\\o\\r\\l\\d -> Hello world\n//\t\"Hello \\\"world\\\"\" -> Hello \"world\"\n//\t'Hello '\\''world'\\''' -> Hello 'world'\nfunc bashUnescape(s string) string {\n\tif len(s) <= 1 {\n\t\treturn s\n\t}\n\tif s[0] == '\\'' && s[len(s)-1] == '\\'' {\n\t\treturn strings.ReplaceAll(s[1:len(s)-1], `'\\''`, `'`)\n\t}\n\tif s[0] == '\"' && s[len(s)-1] == '\"' {\n\t\ts = s[1 : len(s)-1]\n\t\t// Unescape \\\\, \\$, \\\", \\`, and \\!.\n\t\tre := regexp.MustCompile(\"\\\\\\\\([\\\\$\\\"`!\\\\\\\\])\")\n\t\treturn re.ReplaceAllString(s, \"$1\")\n\t}\n\tre := regexp.MustCompile(`\\\\(.)`)\n\treturn re.ReplaceAllString(s, \"$1\")\n}\n\nfunc getConfigFromReader(reader io.Reader) (map[string]string, error) {\n\tre := regexp.MustCompile(`^\\s*([\\w]*)=(.*?)\\s*$`)\n\ts := bufio.NewScanner(reader)\n\tvars := make(map[string]string)\n\tfor s.Scan() {\n\t\tmatch := re.FindStringSubmatch(s.Text())\n\t\tif len(match) >= 3 {\n\t\t\t// TODO(skopecki) Consider allowing variable substitution (e.g., FOOBAR=\"${FOO}/bar\")\n\t\t\tvars[match[1]] = bashUnescape(match[2])\n\t\t}\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn vars, nil\n}\n\nfunc setDefaultVars(vars map[string]string) {\n\t// Keep default in sync with scripts/include-config.sh\n\tif vars[\"CLOUD_ROBOTICS_CONTAINER_REGISTRY\"] == \"\" {\n\t\tvars[\"CLOUD_ROBOTICS_CONTAINER_REGISTRY\"] = \"gcr.io/\" + vars[\"GCP_PROJECT_ID\"]\n\t}\n}\n\n// ReadConfig reads the config.sh from the cloud storage of the given project.\n// All variables specified in the config are returned as dictionary.\n// Uses the following defaults if the variables are not set:\n//\n//\tCLOUD_ROBOTICS_CONTAINER_REGISTRY=\"gcr.io/<GCP_PROJECT_ID>\"\nfunc ReadConfig(project string, opts ...option.ClientOption) (map[string]string, error) {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbkt := client.Bucket(project + \"-cloud-robotics-config\")\n\treader, err := bkt.Object(\"config.sh\").NewReader(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reader.Close()\n\n\tvars, err := getConfigFromReader(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsetDefaultVars(vars)\n\treturn vars, nil\n}\n\n// GetBoolean returns the config value converted to a boolean. It returns the default if the config\n// value has not been set or could not be converted.\nfunc GetBoolean(vars map[string]string, key string, def bool) bool {\n\tval, ok := vars[key]\n\tif ok {\n\t\tif b, err := strconv.ParseBool(val); err == nil {\n\t\t\treturn b\n\t\t} else {\n\t\t\tslog.Error(\"failed to convert config to boolean\",\n\t\t\t\tslog.String(\"Key\", key),\n\t\t\t\tslog.String(\"Value\", val),\n\t\t\t\tilog.Err(err))\n\t\t}\n\t}\n\treturn def\n}\n"
  },
  {
    "path": "src/go/pkg/configutil/config_reader_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage configutil\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestBashUnescape(t *testing.T) {\n\ttests := []struct {\n\t\ts    string\n\t\twant string\n\t}{\n\t\t{\n\t\t\ts:    `foo\\ \\b\\a\\r\\!`,\n\t\t\twant: `foo bar!`,\n\t\t},\n\t\t{\n\t\t\ts:    `\"foo\\ \\\"bar\\\"\\!\"`,\n\t\t\twant: `foo\\ \"bar\"!`,\n\t\t},\n\t\t{\n\t\t\ts:    `'foo\\ '\\''bar'\\''\\!'`,\n\t\t\twant: `foo\\ 'bar'\\!`,\n\t\t},\n\t\t{\n\t\t\ts:    `\"foo\\ \\b\\a\\r\\!'`,\n\t\t\twant: `\"foo bar!'`,\n\t\t},\n\t}\n\n\tfor i, tc := range tests {\n\t\tif got := bashUnescape(tc.s); got != tc.want {\n\t\t\tt.Errorf(\"[%d] bashUnescape(`%s`) = `%s`', want `%s`\", i, tc.s, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestGetConfigFromReader(t *testing.T) {\n\ts := `\nFOO=\"foo\"\n  FOO_BAR1=foo\\ bar\nBAR=buzz\nBAR=bar\n# NOPE1=\"don't take this\"\nNOPE2 = \"or this\"`\n\tr := strings.NewReader(s)\n\twant := map[string]string{\n\t\t\"FOO\":      \"foo\",\n\t\t\"FOO_BAR1\": \"foo bar\",\n\t\t\"BAR\":      \"bar\",\n\t}\n\n\tgot, err := getConfigFromReader(r)\n\tif err != nil {\n\t\tt.Fatalf(\"Got error while reading config: %v\", err)\n\t}\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"getConfigFromReader(`%s`) =\\n%q\\n\\nwant:\\n%q\", s, got, want)\n\t}\n}\n\nfunc TestSetDefaultVars(t *testing.T) {\n\ttests := []struct {\n\t\tv    map[string]string\n\t\twant map[string]string\n\t}{\n\t\t{\n\t\t\tv: map[string]string{\n\t\t\t\t\"GCP_PROJECT_ID\": \"foo\",\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"GCP_PROJECT_ID\":                    \"foo\",\n\t\t\t\t\"CLOUD_ROBOTICS_CONTAINER_REGISTRY\": \"gcr.io/foo\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tv: map[string]string{\n\t\t\t\t\"GCP_PROJECT_ID\":                    \"foo\",\n\t\t\t\t\"CLOUD_ROBOTICS_CONTAINER_REGISTRY\": \"gcr.io/bar\",\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"GCP_PROJECT_ID\":                    \"foo\",\n\t\t\t\t\"CLOUD_ROBOTICS_CONTAINER_REGISTRY\": \"gcr.io/bar\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, tc := range tests {\n\t\tsetDefaultVars(tc.v)\n\t\tif !reflect.DeepEqual(tc.v, tc.want) {\n\t\t\tt.Errorf(\"[%d], got: %v\\nwant %v\", i, tc.v, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestGetBoolean(t *testing.T) {\n\ttests := []struct {\n\t\tv    map[string]string\n\t\tdef  bool\n\t\twant bool\n\t}{\n\t\t{ // good key present\n\t\t\tv:    map[string]string{\"FLAG\": \"true\"},\n\t\t\tdef:  false,\n\t\t\twant: true,\n\t\t},\n\t\t{ // no values, return def\n\t\t\tv:    map[string]string{},\n\t\t\tdef:  false,\n\t\t\twant: false,\n\t\t},\n\t\t{ // key absent, return def\n\t\t\tv:    map[string]string{\"OPTION\": \"true\"},\n\t\t\tdef:  false,\n\t\t\twant: false,\n\t\t},\n\t\t{ // good key, but bad value present, return def\n\t\t\tv:    map[string]string{\"FLAG\": \"I am not a flag\"},\n\t\t\tdef:  false,\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor i, tc := range tests {\n\t\tr := GetBoolean(tc.v, \"FLAG\", tc.def)\n\t\tif r != tc.want {\n\t\t\tt.Errorf(\"[%d] got: %v\\nwant %v\", i, r, tc.want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/controller/approllout/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"controller.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/controller/approllout\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/validation:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/labels:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_client_go//util/workqueue:go_default_library\",\n        \"@io_k8s_helm//pkg/chartutil:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/controller:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/event:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/handler:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/manager:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/reconcile:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/source:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/webhook/admission:go_default_library\",\n        \"@io_k8s_sigs_yaml//:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"controller_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/apis/registry/v1alpha1:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_helm//pkg/chartutil:go_default_library\",\n        \"@io_k8s_sigs_yaml//:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/controller/approllout/controller.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage approllout\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tregistry \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\tcore \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/validation\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/util/workqueue\"\n\t\"k8s.io/helm/pkg/chartutil\"\n\tkclient \"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\tfieldIndexOwners  = \"metadata.ownerReferences.uid\"\n\tfieldIndexAppName = \"spec.appName\"\n\tlabelRobotName    = \"cloudrobotics.com/robot-name\"\n)\n\n// Add adds a controller for the AppRollout resource type\n// to the manager and server.\nfunc Add(ctx context.Context, mgr manager.Manager, baseValues chartutil.Values) error {\n\tr := &Reconciler{\n\t\tkube:       mgr.GetClient(),\n\t\tbaseValues: baseValues,\n\t}\n\tc, err := controller.New(\"approllout\", mgr, controller.Options{\n\t\tReconciler: r,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"create controller\")\n\t}\n\n\terr = mgr.GetCache().IndexField(ctx, &apps.ChartAssignment{}, fieldIndexOwners, indexOwnerReferences)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"add field indexer\")\n\t}\n\terr = mgr.GetCache().IndexField(ctx, &apps.AppRollout{}, fieldIndexAppName, indexAppName)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"add field indexer\")\n\t}\n\n\terr = c.Watch(\n\t\tsource.Kind(mgr.GetCache(), &apps.AppRollout{}),\n\t\t&handler.EnqueueRequestForObject{},\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"watch AppRollouts\")\n\t}\n\t// We don't trigger on ChartAssignment creations since it was either ourselves\n\t// or a CA we don't care about anyway.\n\terr = c.Watch(\n\t\tsource.Kind(mgr.GetCache(), &apps.ChartAssignment{}),\n\t\t// We manually enqueue for the owner reference since handler.EnqueueRequestForOwner\n\t\t// does not work.\n\t\t// TODO: There is an associated bug in the controller-runtime but upgrading to include\n\t\t// https://github.com/kubernetes-sigs/controller-runtime/pull/274 did not resolve the issue.\n\t\t&handler.Funcs{\n\t\t\tDeleteFunc: func(ctx context.Context, evt event.DeleteEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tr.enqueueForOwner(evt.Object, q)\n\t\t\t},\n\t\t\tUpdateFunc: func(ctx context.Context, evt event.UpdateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tr.enqueueForOwner(evt.ObjectNew, q)\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"watch ChartAssignments\")\n\t}\n\t// Determining which rollouts are affected by a robot change is tedious.\n\t// We just enqueue all AppRollouts again.\n\terr = c.Watch(\n\t\tsource.Kind(mgr.GetCache(), &registry.Robot{}),\n\t\t// We log robot events for now while b/125308238 persists.\n\t\t// To mitigate the effects we defer enqueueing in the delete handler\n\t\t// so the robot ideally reappeared before we reconcile.\n\t\t&handler.Funcs{\n\t\t\tCreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tslog.Info(\"AppRollout controller received create event\", slog.String(\"Robot\", e.Object.GetName()))\n\t\t\t\tr.enqueueAll(ctx, q)\n\t\t\t},\n\t\t\tUpdateFunc: func(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\t// Robots don't have the status subresource enabled. Filter updates that didn't\n\t\t\t\t// change robot name or labels.\n\t\t\t\tchange := !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels())\n\t\t\t\tchange = change || e.ObjectOld.GetName() != e.ObjectNew.GetName()\n\t\t\t\tif change {\n\t\t\t\t\tslog.Info(\"AppRollout controller received update event\", slog.String(\"Robot\", e.ObjectNew.GetName()))\n\t\t\t\t\tr.enqueueAll(ctx, q)\n\t\t\t\t}\n\t\t\t},\n\t\t\tDeleteFunc: func(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tslog.Info(\"AppRollout controller received delete event\", slog.String(\"Robot\", e.Object.GetName()))\n\t\t\t\ttime.AfterFunc(3*time.Second, func() {\n\t\t\t\t\tr.enqueueAll(ctx, q)\n\t\t\t\t})\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"watch Robots\")\n\t}\n\terr = c.Watch(\n\t\tsource.Kind(mgr.GetCache(), &apps.App{}),\n\t\t&handler.Funcs{\n\t\t\tCreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tslog.Info(\"AppRollout controller received create event\", slog.String(\"App\", e.Object.GetName()))\n\t\t\t\tr.enqueueForApp(ctx, e.Object, q)\n\t\t\t},\n\t\t\tUpdateFunc: func(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tslog.Info(\"AppRollout controller received update event\", slog.String(\"App\", e.ObjectNew.GetName()))\n\t\t\t\tr.enqueueForApp(ctx, e.ObjectNew, q)\n\t\t\t},\n\t\t\tDeleteFunc: func(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tslog.Info(\"AppRollout controller received delete event\", slog.String(\"App\", e.Object.GetName()))\n\t\t\t\tr.enqueueForApp(ctx, e.Object, q)\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"watch Apps\")\n\t}\n\treturn nil\n}\n\n// enqueueForApp enqueues all AppRollouts for the given app.\nfunc (r *Reconciler) enqueueForApp(ctx context.Context, m metav1.Object, q workqueue.RateLimitingInterface) {\n\tvar rollouts apps.AppRolloutList\n\terr := r.kube.List(ctx, &rollouts, kclient.MatchingFields(map[string]string{fieldIndexAppName: m.GetName()}))\n\tif err != nil {\n\t\tslog.Error(\"List AppRollouts failed\", slog.String(\"appName\", m.GetName()), ilog.Err(err))\n\t\treturn\n\t}\n\tfor _, ar := range rollouts.Items {\n\t\tq.Add(reconcile.Request{\n\t\t\tNamespacedName: types.NamespacedName{Name: ar.Name},\n\t\t})\n\t}\n}\n\n// enqueueForOwner enqueues AppRollouts that are listed in the owner references\n// of the given resource metadata.\nfunc (r *Reconciler) enqueueForOwner(m metav1.Object, q workqueue.RateLimitingInterface) {\n\tfor _, or := range m.GetOwnerReferences() {\n\t\tif or.APIVersion == \"apps.cloudrobotics.com/v1alpha1\" && or.Kind == \"AppRollout\" {\n\t\t\tq.Add(reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{Name: or.Name},\n\t\t\t})\n\t\t}\n\t}\n}\n\n// enqueueAll enqueues all AppRollouts.\nfunc (r *Reconciler) enqueueAll(ctx context.Context, q workqueue.RateLimitingInterface) {\n\tvar rollouts apps.AppRolloutList\n\terr := r.kube.List(ctx, &rollouts)\n\tif err != nil {\n\t\tslog.Error(\"List AppRollouts failed\", ilog.Err(err))\n\t\treturn\n\t}\n\tfor _, ar := range rollouts.Items {\n\t\tq.Add(reconcile.Request{\n\t\t\tNamespacedName: types.NamespacedName{Name: ar.Name},\n\t\t})\n\t}\n}\n\n// Reconciler provides an idempotent function that brings the cluster into a\n// state consistent with the specification of an AppRollout.\ntype Reconciler struct {\n\tkube       kclient.Client\n\tbaseValues chartutil.Values\n}\n\nfunc (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {\n\tvar ar apps.AppRollout\n\terr := r.kube.Get(ctx, req.NamespacedName, &ar)\n\n\tif k8serrors.IsNotFound(err) {\n\t\t// AppRollout was already deleted, nothing to do.\n\t\treturn reconcile.Result{}, nil\n\t} else if err != nil {\n\t\treturn reconcile.Result{}, errors.Wrapf(err, \"get AppRollout %q\", req)\n\t}\n\treturn r.reconcile(ctx, &ar)\n}\n\nfunc (r *Reconciler) reconcile(ctx context.Context, ar *apps.AppRollout) (reconcile.Result, error) {\n\tslog.Info(\"Reconcile AppRollout\",\n\t\tslog.String(\"Name\", ar.Name),\n\t\tslog.String(\"Version\", ar.ResourceVersion))\n\n\t// Apply spec.\n\tvar (\n\t\tcurCAs apps.ChartAssignmentList\n\t\tal     apps.AppList\n\t\trobots registry.RobotList\n\t)\n\n\tar.Status.ObservedGeneration = ar.Generation\n\tar.Status.Assignments = 0\n\tar.Status.SettledAssignments = 0\n\tar.Status.ReadyAssignments = 0\n\tar.Status.FailedAssignments = 0\n\n\t// TODO(coconutruben): consider moving these into a testable function.\n\t// Moving them into generateChartAssignments requires rewriting the\n\t// existing tests.\n\terr := r.kube.List(ctx, &curCAs, kclient.MatchingFields(map[string]string{fieldIndexOwners: string(ar.UID)}))\n\tif err != nil {\n\t\treturn reconcile.Result{}, errors.Wrapf(err, \"list ChartAssignments for owner UID %s\", ar.UID)\n\t}\n\n\tif err := r.kube.List(ctx, &robots); err != nil {\n\t\treturn reconcile.Result{}, errors.Wrap(err, \"list all Robots\")\n\t}\n\n\tif err := r.kube.List(ctx, &al, kclient.MatchingLabels{labelAppName: ar.Spec.AppName}); err != nil {\n\t\treturn reconcile.Result{}, errors.Wrap(err, \"list all App Versions\")\n\t}\n\n\t// There might be old Apps laying around that do not conform to this\n\t// methodology yet. However, those Apps also do not use the version\n\t// mechanism. Therefore, we can just look for that App once, and put\n\t// it into our map if it's not there yet.\n\tappFound := func(name string) bool {\n\t\tfor idx := range al.Items {\n\t\t\tif al.Items[idx].Name == name {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\tif !appFound(ar.Spec.AppName) {\n\t\t// We might not need the canonical app at all, if all the rollout\n\t\t// entries are asking for versioned apps. Do not fail here yet,\n\t\t// if we needed it, it will fail in generateChartAssignments\n\t\tapp := apps.App{}\n\t\tif err := r.kube.Get(ctx, kclient.ObjectKey{Name: ar.Spec.AppName}, &app); err == nil {\n\t\t\t// If we're here, the App is both:\n\t\t\t// - app.Name == ar.Spec.AppName.\n\t\t\t// - not found using the app-name label. This object is a copy so\n\t\t\t// we can just write the label in there, so generateChartAssignments\n\t\t\t// can safely assume the label is always there.\n\t\t\tif app.Labels == nil {\n\t\t\t\tapp.Labels = map[string]string{}\n\t\t\t}\n\t\t\tapp.Labels[labelAppName] = app.Name\n\t\t\tapp.Labels[labelAppVersion] = \"\"\n\t\t\tal.Items = append(al.Items, app)\n\t\t}\n\t}\n\n\twantCAs, err := generateChartAssignments(al.Items, robots.Items, ar, r.baseValues)\n\tif err != nil {\n\t\tif _, ok := errors.Cause(err).(errRobotSelectorOverlap); ok {\n\t\t\treturn reconcile.Result{}, r.updateErrorStatus(ctx, ar, err.Error())\n\t\t}\n\t\treturn reconcile.Result{}, errors.Wrap(err, \"generate ChartAssignments\")\n\t}\n\n\t// ChartAssignments that are no longer wanted. We pre-populate it with\n\t// all existing CAs and remove those that we want to keep\n\tdropCAs := map[string]apps.ChartAssignment{}\n\n\tfor _, ca := range curCAs.Items {\n\t\tdropCAs[ca.Name] = ca\n\t}\n\t// Create or update ChartAssignments. Only update ChartAssignments if the rollout's\n\t// spec or labels have been updated.\n\tfor _, ca := range wantCAs {\n\t\t_true := true\n\t\tsetOwnerReference(&ca.ObjectMeta, metav1.OwnerReference{\n\t\t\tAPIVersion:         ar.APIVersion,\n\t\t\tKind:               ar.Kind,\n\t\t\tName:               ar.Name,\n\t\t\tUID:                ar.UID,\n\t\t\tBlockOwnerDeletion: &_true,\n\t\t\tController:         &_true,\n\t\t})\n\t\tprev, exists := dropCAs[ca.Name]\n\t\tdelete(dropCAs, ca.Name)\n\n\t\tif !exists {\n\t\t\tif err := r.kube.Create(ctx, ca); err != nil {\n\t\t\t\treturn reconcile.Result{}, errors.Wrapf(err, \"create ChartAssignment %q\", ca.Name)\n\t\t\t}\n\t\t\tslog.Info(\"Created ChartAssignment\", slog.String(\"Name\", ca.Name))\n\t\t\tcontinue\n\t\t}\n\t\tif changed, err := chartAssignmentChanged(&prev, ca); err != nil {\n\t\t\treturn reconcile.Result{}, errors.Wrap(err, \"check ChartAssignment changed\")\n\t\t} else if !changed {\n\t\t\tcontinue\n\t\t}\n\t\tca.ResourceVersion = prev.ResourceVersion\n\t\tif err := r.kube.Update(ctx, ca); err != nil {\n\t\t\treturn reconcile.Result{}, errors.Wrapf(err, \"update ChartAssignment %q\", ca.Name)\n\t\t}\n\t\tslog.Info(\"Updated ChartAssignment\", slog.String(\"Name\", ca.Name))\n\t}\n\t// Delete obsolete assignments.\n\tfor _, ca := range dropCAs {\n\t\tif err := r.kube.Delete(ctx, &ca); err != nil {\n\t\t\treturn reconcile.Result{}, errors.Wrapf(err, \"delete ChartAssignment %q\", ca.Name)\n\t\t}\n\t\tslog.Info(\"Deleted ChartAssignment\", slog.String(\"Name\", ca.Name))\n\t}\n\n\tsetStatus(ar, len(wantCAs), curCAs.Items)\n\n\tif err := r.kube.Status().Update(ctx, ar); err != nil {\n\t\treturn reconcile.Result{}, errors.Wrap(err, \"update status\")\n\t}\n\treturn reconcile.Result{}, nil\n}\n\nfunc (r *Reconciler) updateErrorStatus(ctx context.Context, ar *apps.AppRollout, msg string) error {\n\tsetCondition(ar, apps.AppRolloutConditionSettled, core.ConditionFalse, msg)\n\tif err := r.kube.Status().Update(ctx, ar); err != nil {\n\t\treturn errors.Wrap(err, \"update status\")\n\t}\n\treturn nil\n}\n\nfunc setStatus(ar *apps.AppRollout, numWantCAs int, curCAs []apps.ChartAssignment) {\n\t// Update status.\n\tar.Status.Assignments = int64(numWantCAs)\n\n\tfor _, ca := range curCAs {\n\t\tswitch ca.Status.Phase {\n\t\tcase apps.ChartAssignmentPhaseReady:\n\t\t\tar.Status.ReadyAssignments++\n\t\t\tar.Status.SettledAssignments++\n\t\tcase apps.ChartAssignmentPhaseSettled:\n\t\t\tar.Status.SettledAssignments++\n\t\tcase apps.ChartAssignmentPhaseFailed:\n\t\t\tar.Status.FailedAssignments++\n\t\t}\n\t}\n\tif got, want := ar.Status.SettledAssignments, ar.Status.Assignments; got == want {\n\t\tsetCondition(ar, apps.AppRolloutConditionSettled, core.ConditionTrue, \"\")\n\t} else {\n\t\tsetCondition(ar, apps.AppRolloutConditionSettled, core.ConditionFalse,\n\t\t\tfmt.Sprintf(\"%d/%d ChartAssignments settled\", got, want))\n\t}\n\tif got, want := ar.Status.ReadyAssignments, ar.Status.Assignments; got == want {\n\t\tsetCondition(ar, apps.AppRolloutConditionReady, core.ConditionTrue, \"\")\n\t} else {\n\t\tsetCondition(ar, apps.AppRolloutConditionReady, core.ConditionFalse,\n\t\t\tfmt.Sprintf(\"%d/%d ChartAssignments ready\", got, want))\n\t}\n}\n\n// setCondition adds or updates a condition. Existing conditions are detected based on the Type field.\nfunc setCondition(ar *apps.AppRollout, t apps.AppRolloutConditionType, s core.ConditionStatus, msg string) {\n\tnow := metav1.Now()\n\n\tfor i, c := range ar.Status.Conditions {\n\t\tif c.Type != t {\n\t\t\tcontinue\n\t\t}\n\t\t// Update existing condition.\n\t\tif c.Status != s || c.Message != msg {\n\t\t\tc.LastUpdateTime = now\n\t\t}\n\t\tif c.Status != s {\n\t\t\tc.LastTransitionTime = now\n\t\t}\n\t\tc.Message = msg\n\t\tc.Status = s\n\t\tar.Status.Conditions[i] = c\n\t\treturn\n\t}\n\t// Condition set for the first time.\n\tar.Status.Conditions = append(ar.Status.Conditions, apps.AppRolloutCondition{\n\t\tType:               t,\n\t\tLastUpdateTime:     now,\n\t\tLastTransitionTime: now,\n\t\tStatus:             s,\n\t\tMessage:            msg,\n\t})\n}\n\n// chartAssignmentChanged returns true if the CA's labels, annotations, or spec changed.\nfunc chartAssignmentChanged(prev, cur *apps.ChartAssignment) (bool, error) {\n\tif !reflect.DeepEqual(prev.Labels, cur.Labels) {\n\t\treturn true, nil\n\t}\n\tif !reflect.DeepEqual(prev.Annotations, cur.Annotations) {\n\t\treturn true, nil\n\t}\n\tprevSpec, err := yaml.Marshal(prev.Spec)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcurSpec, err := yaml.Marshal(cur.Spec)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn !bytes.Equal(prevSpec, curSpec), nil\n}\n\ntype errRobotSelectorOverlap string\n\nfunc (r errRobotSelectorOverlap) Error() string {\n\treturn fmt.Sprintf(\"robot %q was selected multiple times\", string(r))\n}\n\n// generateChartAssignments returns a list of all cloud and robot ChartAssignments\n// for the given app, its rollout, and set of robots.\nfunc generateChartAssignments(\n\tal []apps.App,\n\trobots []registry.Robot,\n\trollout *apps.AppRollout,\n\tbaseValues chartutil.Values,\n) ([]*apps.ChartAssignment, error) {\n\n\tvar (\n\t\t// Different entries might request different app versions. This map\n\t\t// is used to only retrieve them once.\n\t\tappVersions = map[string]*apps.App{}\n\t\tcas         []*apps.ChartAssignment\n\t\t// Robots that matched selectors for the rollout and which will be\n\t\t// passed to the cloud chart.\n\t\tselectedRobots = map[string]*registry.Robot{}\n\t)\n\n\tfor _, app := range al {\n\t\tv, ok := app.Labels[labelAppVersion]\n\t\tif !ok {\n\t\t\t// If only app-name is defined, it is an unversioned app.\n\t\t\tv = \"\"\n\t\t}\n\t\tif _, ok := appVersions[v]; ok {\n\t\t\tslog.Info(\"App already known. Going to ignore App Object for the same app/version\",\n\t\t\t\tslog.String(\"Known Name\", rollout.Spec.AppName),\n\t\t\t\tslog.String(\"Known Version\", v),\n\t\t\t\tslog.String(\"App Object\", app.Name))\n\t\t} else {\n\t\t\t// only add to map if this is the first time we're adding this\n\t\t\t// version for the app. The validator should ensure that this\n\t\t\t// overwrite cannot happen, but apps might be legacy, or have\n\t\t\t// bypassed validation.\n\t\t\tappVersions[v] = &app\n\t\t}\n\t}\n\tfor _, rcomp := range rollout.Spec.Robots {\n\t\trobots, err := matchingRobots(robots, rcomp.Selector)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"select robots\")\n\t\t}\n\t\t// map is populated by for all the rcomp.Version, no need to check ok\n\t\tapp, ok := appVersions[rcomp.Version]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"no App %q (Version: %q) found\", rollout.Spec.AppName, rcomp.Version)\n\t\t}\n\t\tcomps := app.Spec.Components\n\t\tfor i := range robots {\n\t\t\t// Ensure we don't pass a pointer to the most recent loop item.\n\t\t\tr := &robots[i]\n\t\t\t// No robot must be selected multiple times.\n\t\t\tif _, ok := selectedRobots[r.Name]; ok {\n\t\t\t\treturn nil, errRobotSelectorOverlap(r.Name)\n\t\t\t}\n\t\t\tselectedRobots[r.Name] = r\n\n\t\t\tif comps.Robot.Name != \"\" || comps.Robot.Inline != \"\" {\n\t\t\t\tcas = append(cas, newRobotChartAssignment(r, app, rollout, &rcomp, baseValues))\n\t\t\t}\n\t\t}\n\t}\n\t// The cloud has has no version, just the canonical version. We might\n\t// not have it due to no robot using it.\n\tif app, ok := appVersions[\"\"]; ok {\n\t\tcomps := app.Spec.Components\n\t\tif comps.Cloud.Name != \"\" || comps.Cloud.Inline != \"\" {\n\t\t\t// Turn robot map into a sorted slice so we produce deterministic outputs.\n\t\t\t// (Go randomizes map iteration.)\n\t\t\trobots := make([]*registry.Robot, 0, len(selectedRobots))\n\t\t\tfor _, r := range selectedRobots {\n\t\t\t\trobots = append(robots, r)\n\t\t\t}\n\t\t\tsort.Slice(robots, func(i, j int) bool {\n\t\t\t\treturn robots[i].Name < robots[j].Name\n\t\t\t})\n\t\t\tcas = append(cas, newCloudChartAssignment(app, rollout, baseValues, robots...))\n\t\t}\n\t} else if rollout.Spec.Cloud.Values != nil {\n\t\tslog.Info(\"No canonical version of App. There won't be a Cloud ChartAssignment. AppRollout defines cloud values.\",\n\t\t\tslog.String(\"App Name\", rollout.Spec.AppName),\n\t\t\tslog.String(\"Rollout Name\", rollout.Name))\n\t}\n\tsort.Slice(cas, func(i, j int) bool {\n\t\treturn cas[i].Name < cas[j].Name\n\t})\n\treturn cas, nil\n}\n\n// newCloudChartAssignment generates a new ChartAssignment for the cloud cluster\n// from an app, it's rollout, a set of base configuration values,\n// and a list of robots matched by the rollout.\nfunc newCloudChartAssignment(\n\tapp *apps.App,\n\trollout *apps.AppRollout,\n\tvalues chartutil.Values,\n\trobots ...*registry.Robot,\n) *apps.ChartAssignment {\n\tca := newBaseChartAssignment(app, rollout, &app.Spec.Components.Cloud)\n\n\tca.Name = chartAssignmentName(rollout.Name, compTypeCloud, \"\")\n\tca.Spec.ClusterName = \"cloud\"\n\n\t// Generate robot values list that's injected into the cloud chart.\n\tvar robotValuesList []robotValues\n\tfor _, r := range robots {\n\t\trobotValuesList = append(robotValuesList, robotValues{\n\t\t\tName: r.Name,\n\t\t})\n\t}\n\tvals := chartutil.Values{}\n\tvals.MergeInto(values)\n\tvals.MergeInto(chartutil.Values(rollout.Spec.Cloud.Values))\n\tvals.MergeInto(chartutil.Values{\"robots\": robotValuesList})\n\n\tca.Spec.Chart.Values = apps.ConfigValues(vals)\n\n\treturn ca\n}\n\n// newRobotChartAssignment generates a new ChartAssignment for a robot cluster\n// from an app, its rollout, and a set of base configuration values.\nfunc newRobotChartAssignment(\n\trobot *registry.Robot,\n\tapp *apps.App,\n\trollout *apps.AppRollout,\n\tspec *apps.AppRolloutSpecRobot,\n\tvalues chartutil.Values,\n) *apps.ChartAssignment {\n\tca := newBaseChartAssignment(app, rollout, &app.Spec.Components.Robot)\n\n\tca.Name = chartAssignmentName(rollout.Name, compTypeRobot, robot.Name)\n\tsetLabel(&ca.ObjectMeta, labelRobotName, robot.Name)\n\n\tca.Spec.ClusterName = robot.Name\n\tif spec.Version != \"\" {\n\t\tca.Spec.Chart.Version = spec.Version\n\t}\n\n\tvals := chartutil.Values{}\n\tvals.MergeInto(values)\n\tvals.MergeInto(chartutil.Values(spec.Values))\n\tvals.MergeInto(chartutil.Values{\"robot\": robotValues{Name: robot.Name}})\n\n\tca.Spec.Chart.Values = apps.ConfigValues(vals)\n\n\treturn ca\n}\n\n// newChartAssignments returns a new ChartAssignments that's initialized with\n// all values that are fixed for the app, its rollout, and component.\nfunc newBaseChartAssignment(app *apps.App, rollout *apps.AppRollout, comp *apps.AppComponent) *apps.ChartAssignment {\n\tvar ca apps.ChartAssignment\n\n\t// Clone labels and annotations. Just setting the map reference\n\t// would cause the map to be shared across objects.\n\tfor k, v := range rollout.Labels {\n\t\tsetLabel(&ca.ObjectMeta, k, v)\n\t}\n\tfor k, v := range rollout.Annotations {\n\t\tif k != core.LastAppliedConfigAnnotation {\n\t\t\tsetAnnotation(&ca.ObjectMeta, k, v)\n\t\t}\n\t}\n\tca.Spec.NamespaceName = appNamespaceName(rollout.Name)\n\n\tif comp.Name != \"\" {\n\t\tca.Spec.Chart = apps.AssignedChart{\n\t\t\tRepository: app.Spec.Repository,\n\t\t\tVersion:    app.Spec.Version,\n\t\t\tName:       comp.Name,\n\t\t}\n\t}\n\tca.Spec.Chart.Inline = comp.Inline\n\n\treturn &ca\n}\n\n// matchingRobots returns the subset of robots that pass the given robot selector.\n// It returns an error if the selector is invalid.\nfunc matchingRobots(robots []registry.Robot, sel *apps.RobotSelector) ([]registry.Robot, error) {\n\tif sel.Any != nil && *sel.Any {\n\t\treturn robots, nil\n\t}\n\tif sel.LabelSelector == nil {\n\t\treturn nil, nil\n\t}\n\tselector, err := metav1.LabelSelectorAsSelector(sel.LabelSelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar res []registry.Robot\n\tfor _, r := range robots {\n\t\tif selector.Matches(labels.Set(r.Labels)) {\n\t\t\tres = append(res, r)\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc appNamespaceName(rollout string) string {\n\treturn fmt.Sprintf(\"app-%s\", rollout)\n}\n\ntype componentType string\n\nconst (\n\tcompTypeRobot componentType = \"robot\"\n\tcompTypeCloud               = \"cloud\"\n)\n\nfunc chartAssignmentName(rollout string, typ componentType, robot string) string {\n\tif robot != \"\" {\n\t\treturn fmt.Sprintf(\"%s-%s-%s\", rollout, typ, robot)\n\t}\n\treturn fmt.Sprintf(\"%s-%s\", rollout, typ)\n}\n\n// robotValues is the struct that is passed into the cloud chart configuration\n// for each robot matched by a rollout.\ntype robotValues struct {\n\tName string `json:\"name\"`\n}\n\nfunc setLabel(o *metav1.ObjectMeta, k, v string) {\n\tif o.Labels == nil {\n\t\to.Labels = map[string]string{}\n\t}\n\to.Labels[k] = v\n}\n\nfunc setAnnotation(o *metav1.ObjectMeta, k, v string) {\n\tif o.Annotations == nil {\n\t\to.Annotations = map[string]string{}\n\t}\n\to.Annotations[k] = v\n}\n\n// setOwnerReference adds or updates an owner reference. Existing references\n// are detected based on the UID field.\nfunc setOwnerReference(om *metav1.ObjectMeta, ref metav1.OwnerReference) {\n\tfor i, or := range om.OwnerReferences {\n\t\tif ref.UID == or.UID {\n\t\t\tom.OwnerReferences[i] = ref\n\t\t\treturn\n\t\t}\n\t}\n\tom.OwnerReferences = append(om.OwnerReferences, ref)\n}\n\n// indexOwnerReferences indexes resources by the UIDs of their owner references.\nfunc indexOwnerReferences(o kclient.Object) (vs []string) {\n\tca := o.(*apps.ChartAssignment)\n\tfor _, or := range ca.OwnerReferences {\n\t\tvs = append(vs, string(or.UID))\n\t}\n\treturn vs\n}\n\nfunc indexAppName(o kclient.Object) []string {\n\tar := o.(*apps.AppRollout)\n\treturn []string{ar.Spec.AppName}\n}\n\nconst (\n\t// canonical name of an app\n\tlabelAppName = \"cloudrobotics.com/app-name\"\n\t// version of that app. Note, the default version of the app has\n\t// a version label of \"\"\n\tlabelAppVersion = \"cloudrobotics.com/app-version\"\n)\n\n// NewAppValidationWebhook returns a new webhook that validates Apps.\n//\n// This pertains to multiple versions of the same app, so that the labels\n// defined above are in sync with the name of the App.\n// The policy is\n// - an unversioned app defines\n//   - cloudrobotics.com/app-name\n//   - (optionally): cloudrobotics.com/app-version with a \"\" value\n//     this must match the name of the object\n//\n// - a versioned app defines\n//   - cloudrobotics.com/app-name\n//   - cloudrobotics.com/app-version\n//     the name of the App object must match LOWERCASE([app-name].v[app-version])\nfunc NewAppValidationWebhook(mgr manager.Manager) *admission.Webhook {\n\treturn &admission.Webhook{Handler: newAppValidator(mgr.GetScheme())}\n}\n\n// appValidator implements a validation webhook.\ntype appValidator struct {\n\tdecoder runtime.Decoder\n}\n\nfunc newAppValidator(sc *runtime.Scheme) *appValidator {\n\treturn &appValidator{\n\t\tdecoder: serializer.NewCodecFactory(sc).UniversalDeserializer(),\n\t}\n}\n\nfunc (v *appValidator) Handle(_ context.Context, req admission.Request) admission.Response {\n\tcur := &apps.App{}\n\tif err := runtime.DecodeInto(v.decoder, req.AdmissionRequest.Object.Raw, cur); err != nil {\n\t\treturn admission.Errored(http.StatusBadRequest, err)\n\t}\n\tif err := appValidate(cur); err != nil {\n\t\treturn admission.Denied(err.Error())\n\t}\n\treturn admission.Allowed(\"\")\n}\n\nfunc appValidate(cur *apps.App) error {\n\tname := cur.Name\n\tappName, anok := cur.Labels[labelAppName]\n\tappVersion, avok := cur.Labels[labelAppVersion]\n\tif anok {\n\t\tif avok && appVersion != \"\" {\n\t\t\t// both name and version are defined\n\t\t\tename := strings.ToLower(fmt.Sprintf(\"%s.v%s\", appName, appVersion))\n\t\t\tif ename != name {\n\t\t\t\treturn fmt.Errorf(\"%q=%q, %q=%q: expected object name %q, got %q\", labelAppName, appName, labelAppVersion, appVersion, ename, name)\n\t\t\t}\n\t\t} else {\n\t\t\t// only name is defined\n\t\t\tif appName != name {\n\t\t\t\treturn fmt.Errorf(\"%q=%q, undefined %q: expected object name %q, got %q\", labelAppName, appName, labelAppVersion, appName, name)\n\t\t\t}\n\t\t}\n\t}\n\t// neither is defined, we're dealing with a legacy app\n\treturn nil\n}\n\n// NewAppRolloutValidationWebhook returns a new webhook that validates AppRollouts.\nfunc NewAppRolloutValidationWebhook(mgr manager.Manager) *admission.Webhook {\n\treturn &admission.Webhook{Handler: newAppRolloutValidator(mgr.GetScheme())}\n}\n\n// appRolloutValidator implements a validation webhook.\ntype appRolloutValidator struct {\n\tdecoder runtime.Decoder\n}\n\nfunc newAppRolloutValidator(sc *runtime.Scheme) *appRolloutValidator {\n\treturn &appRolloutValidator{\n\t\tdecoder: serializer.NewCodecFactory(sc).UniversalDeserializer(),\n\t}\n}\n\nfunc (v *appRolloutValidator) Handle(_ context.Context, req admission.Request) admission.Response {\n\tcur := &apps.AppRollout{}\n\n\tif err := runtime.DecodeInto(v.decoder, req.AdmissionRequest.Object.Raw, cur); err != nil {\n\t\treturn admission.Errored(http.StatusBadRequest, err)\n\t}\n\tif err := appRolloutValidate(cur); err != nil {\n\t\treturn admission.Denied(err.Error())\n\t}\n\treturn admission.Allowed(\"\")\n}\n\nfunc appRolloutValidate(cur *apps.AppRollout) error {\n\tif cur.Spec.AppName == \"\" {\n\t\treturn errors.New(\"app name missing\")\n\t}\n\terrs := validation.NameIsDNSSubdomain(cur.Spec.AppName, false)\n\tif len(errs) > 0 {\n\t\treturn errors.Errorf(\"validate app name: %s\", strings.Join(errs, \", \"))\n\t}\n\tif _, ok := cur.Spec.Cloud.Values[\"robots\"]; ok {\n\t\treturn errors.Errorf(\".spec.cloud.values.robots is a reserved field and must not be set\")\n\t}\n\tfor i, r := range cur.Spec.Robots {\n\t\tif _, ok := r.Values[\"robot\"]; ok {\n\t\t\treturn errors.Errorf(\".spec.robots[].values.robot is a reserved field and must not be set\")\n\t\t}\n\t\tif r.Selector == nil {\n\t\t\treturn errors.Errorf(\"no selector provided for robots %d\", i)\n\t\t}\n\t\t// Reject if a selector has neither a matcher nor `any` set.\n\t\t// This mostly helps catching missing `matchLabels`.\n\t\tif r.Selector.Any == nil && r.Selector.LabelSelector == nil {\n\t\t\treturn errors.Errorf(\"empty selector for robots %d (matchLabels not specified?)\", i)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/pkg/controller/approllout/controller_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage approllout\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\tregistry \"github.com/googlecloudrobotics/core/src/go/pkg/apis/registry/v1alpha1\"\n\tcore \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/helm/pkg/chartutil\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc marshalYAML(t *testing.T, v interface{}) string {\n\tt.Helper()\n\tb, err := yaml.Marshal(v)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn string(b)\n}\n\nfunc unmarshalYAML(t *testing.T, v interface{}, s string) {\n\tt.Helper()\n\tif err := yaml.Unmarshal([]byte(strings.TrimSpace(s)), v); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc verifyChartAssignment(t *testing.T, want, got *apps.ChartAssignment) {\n\tt.Helper()\n\t// Compare serialized YAML for easier diff detection and to avoid complicated\n\t// comparisons for map[string]interface{} values.\n\twantStr := marshalYAML(t, want)\n\tgotStr := marshalYAML(t, got)\n\n\tif wantStr != gotStr {\n\t\tt.Fatalf(\"expected ChartAssignment: \\n%s\\ngot:\\n%s\\n\", wantStr, gotStr)\n\t}\n}\n\nfunc TestNewRobotChartAssignment(t *testing.T) {\n\tvar app apps.App\n\tunmarshalYAML(t, &app, `\nmetadata:\n  name: foo\nspec:\n  repository: https://example.org/helm\n  version: 1.2.3\n  components:\n    robot:\n      name: foo-robot\n      inline: abcdefgh\n\t`)\n\n\tvar rollout apps.AppRollout\n\tunmarshalYAML(t, &rollout, `\nmetadata:\n  name: foo-rollout\n  labels:\n    lkey1: lval1\n  annotations:\n    akey1: aval1\n  namespace: default\nspec:\n  appName: prometheus\n  robots:\n  - selector:\n      any: true\n    values:\n      foo1: bar1\n    version: 1.2.4\n `)\n\n\tvar robot registry.Robot\n\tunmarshalYAML(t, &robot, `\nmetadata:\n  name: robot1\n\t`)\n\n\tbaseValues := chartutil.Values{\n\t\t\"foo2\": \"bar2\",\n\t}\n\n\tvar expected apps.ChartAssignment\n\tunmarshalYAML(t, &expected, `\nmetadata:\n  name: foo-rollout-robot-robot1\n  labels:\n    lkey1: lval1\n    cloudrobotics.com/robot-name: robot1\n  annotations:\n    akey1: aval1\nspec:\n  clusterName: robot1\n  namespaceName: app-foo-rollout\n  chart:\n    repository: https://example.org/helm\n    version: 1.2.4\n    name: foo-robot\n    inline: abcdefgh\n    values:\n      robot:\n        name: robot1\n      foo1: bar1\n      foo2: bar2\n\t`)\n\n\tresult := newRobotChartAssignment(&robot, &app, &rollout, &rollout.Spec.Robots[0], baseValues)\n\tverifyChartAssignment(t, &expected, result)\n}\n\nfunc TestNewCloudChartAssignment(t *testing.T) {\n\tvar app apps.App\n\tunmarshalYAML(t, &app, `\nmetadata:\n  name: foo\nspec:\n  repository: https://example.org/helm\n  version: 1.2.3\n  components:\n    cloud:\n      name: foo-cloud\n      inline: abcdefgh\n\t`)\n\n\tvar rollout apps.AppRollout\n\tunmarshalYAML(t, &rollout, `\nmetadata:\n  name: foo-rollout\n  labels:\n    lkey1: lval1\n  annotations:\n    akey1: aval1\n  namespace: default\nspec:\n  appName: prometheus\n  cloud:\n    values:\n      robots: should_be_overwritten\n      foo1: bar1\n `)\n\n\tvar robot1, robot2 registry.Robot\n\tunmarshalYAML(t, &robot1, `\nmetadata:\n  name: robot1\n\t`)\n\tunmarshalYAML(t, &robot2, `\nmetadata:\n  name: robot2\n\t`)\n\n\tbaseValues := chartutil.Values{\n\t\t\"foo2\": \"bar2\",\n\t}\n\n\tvar expected apps.ChartAssignment\n\tunmarshalYAML(t, &expected, `\nmetadata:\n  name: foo-rollout-cloud\n  labels:\n    lkey1: lval1\n  annotations:\n    akey1: aval1\nspec:\n  clusterName: cloud\n  namespaceName: app-foo-rollout\n  chart:\n    repository: https://example.org/helm\n    version: 1.2.3\n    name: foo-cloud\n    inline: abcdefgh\n    values:\n      robots:\n      - name: robot1\n      - name: robot2\n      foo1: bar1\n      foo2: bar2\n\t`)\n\n\tresult := newCloudChartAssignment(&app, &rollout, baseValues, &robot1, &robot2)\n\tverifyChartAssignment(t, &expected, result)\n}\n\nfunc TestGenerateChartAssignments(t *testing.T) {\n\tvar app apps.App\n\tunmarshalYAML(t, &app, `\nmetadata:\n  name: foo\nspec:\n  components:\n    cloud:\n      inline: inline-cloud\n    robot:\n      inline: inline-robot\n\t`)\n\n\tvar robots [3]registry.Robot\n\n\tunmarshalYAML(t, &robots[0], `\nmetadata:\n  name: robot1\n\t`)\n\tunmarshalYAML(t, &robots[1], `\nmetadata:\n  name: robot2\n  labels:\n    a: b\n\t`)\n\tunmarshalYAML(t, &robots[2], `\nmetadata:\n  name: robot3\n  labels:\n    a: c\n\t`)\n\n\tbaseValues := chartutil.Values{\n\t\t\"foo2\": \"bar2\",\n\t}\n\n\t// Rollout with two selectors that select robot1 and robot3 respectively.\n\t// robot2 is not matched at all.\n\tvar rollout apps.AppRollout\n\tunmarshalYAML(t, &rollout, `\nmetadata:\n  name: foo-rollout\n  namespace: default\nspec:\n  appName: foo\n  cloud:\n    values:\n      robots: should_be_overwritten\n      foo1: bar1\n  robots:\n  # robot1\n  - selector:\n      matchExpressions:\n      - {key: a, operator: DoesNotExist}\n  # robot3\n  - selector:\n      matchLabels:\n        a: c\n    values:\n      foo3: bar3\n\t`)\n\n\tvar expected [3]apps.ChartAssignment\n\tunmarshalYAML(t, &expected[0], `\nmetadata:\n  name: foo-rollout-cloud\nspec:\n  clusterName: cloud\n  namespaceName: app-foo-rollout\n  chart:\n    inline: inline-cloud\n    values:\n      foo1: bar1\n      foo2: bar2\n      robots:\n      - name: robot1\n      - name: robot3\n\t`)\n\tunmarshalYAML(t, &expected[1], `\nmetadata:\n  name: foo-rollout-robot-robot1\n  labels:\n    cloudrobotics.com/robot-name: robot1\nspec:\n  clusterName: robot1\n  namespaceName: app-foo-rollout\n  chart:\n    inline: inline-robot\n    values:\n      robot:\n        name: robot1\n      foo2: bar2\n      `)\n\tunmarshalYAML(t, &expected[2], `\nmetadata:\n  name: foo-rollout-robot-robot3\n  labels:\n    cloudrobotics.com/robot-name: robot3\nspec:\n  clusterName: robot3\n  namespaceName: app-foo-rollout\n  chart:\n    inline: inline-robot\n    values:\n      robot:\n        name: robot3\n      foo2: bar2\n      foo3: bar3\n      `)\n\n\tal := []apps.App{app}\n\tcas, err := generateChartAssignments(al, robots[:], &rollout, baseValues)\n\tif err != nil {\n\t\tt.Fatalf(\"Generate failed: %s\", err)\n\t}\n\tif len(cas) != len(expected) {\n\t\tt.Errorf(\"Expected %d ChartAssignments, got %d\", len(expected), len(cas))\n\t}\n\tfor i, ca := range cas {\n\t\tverifyChartAssignment(t, &expected[i], ca)\n\t}\n}\n\n// generateApp will generate an app for testing\nfunc generateApp(name, version, robotPayload, cloudPayload string) apps.App {\n\treturn apps.App{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: name,\n\t\t\tLabels: map[string]string{\n\t\t\t\tlabelAppName:    name,\n\t\t\t\tlabelAppVersion: version,\n\t\t\t},\n\t\t},\n\t\tSpec: apps.AppSpec{\n\t\t\tComponents: apps.AppComponents{\n\t\t\t\tCloud: apps.AppComponent{\n\t\t\t\t\tName:   \"cloud\",\n\t\t\t\t\tInline: cloudPayload,\n\t\t\t\t},\n\t\t\t\tRobot: apps.AppComponent{\n\t\t\t\t\tName:   \"robot\",\n\t\t\t\t\tInline: robotPayload,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\n// generateRobot will generate a robot named |name| with the labels\nfunc generateRobot(name string, labels map[string]string) registry.Robot {\n\treturn registry.Robot{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:   name,\n\t\t\tLabels: labels,\n\t\t},\n\t}\n}\n\n// generateRollout will create a rollout pointing at AppName\nfunc generateRollout(name, appName string) apps.AppRollout {\n\treturn apps.AppRollout{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: name,\n\t\t},\n\t\tSpec: apps.AppRolloutSpec{\n\t\t\tAppName: appName,\n\t\t},\n\t}\n}\n\n// addRobotToRollout will add a robot component with the match label & version.\nfunc addRobotToRollout(ar *apps.AppRollout, matchLabel, matchValue, version string) {\n\tar.Spec.Robots = append(ar.Spec.Robots, apps.AppRolloutSpecRobot{\n\t\tVersion: version,\n\t\tSelector: &apps.RobotSelector{\n\t\t\tLabelSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\tmatchLabel: matchValue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n\n// The tests below deal with vApp, this is a short form for versioned App\n\n// TestGenerateChartAssignments_vApps tests rollout with versioned Apps\nfunc TestGenerateChartAssignments_vApps(t *testing.T) {\n\tappName := \"myapp\"\n\tappVersion := \"17\"\n\ttestLabel := \"test-label\"\n\ttestValueA := \"test-value\"\n\ttestValueB := \"test-value-2\"\n\tal := []apps.App{\n\t\tgenerateApp(appName, \"\", \"testpayload\", \"\"),\n\t\tgenerateApp(appName, appVersion, \"testpayload\", \"\"),\n\t}\n\trobots := []registry.Robot{\n\t\tgenerateRobot(\"robot1\", map[string]string{\n\t\t\ttestLabel: testValueA,\n\t\t}),\n\t\tgenerateRobot(\"robot2\", map[string]string{\n\t\t\ttestLabel: testValueA,\n\t\t}),\n\t\tgenerateRobot(\"robot3\", map[string]string{\n\t\t\ttestLabel: testValueB,\n\t\t}),\n\t}\n\trollout := generateRollout(\"test\", appName)\n\t// a versioned app matching robots testValueA\n\taddRobotToRollout(&rollout, testLabel, testValueA, appVersion)\n\t// the canonical app matching robots testValueB\n\taddRobotToRollout(&rollout, testLabel, testValueB, \"\")\n\t// Simply test that this works, and does not throw errors.\n\t_, err := generateChartAssignments(al, robots[:], &rollout, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Generate failed: %v\", err)\n\t}\n}\n\n// TestGenerateChartAssignments_vAppMissing tests a missing App version\nfunc TestGenerateChartAssignments_vAppMissing(t *testing.T) {\n\tappName := \"myapp\"\n\tappVersion := \"17\"\n\tnewAppVersion := \"18\"\n\ttestLabel := \"test-label\"\n\ttestValueA := \"test-value\"\n\tal := []apps.App{\n\t\tgenerateApp(appName, appVersion, \"testpayload\", \"\"),\n\t}\n\trobots := []registry.Robot{\n\t\tgenerateRobot(\"robot1\", map[string]string{\n\t\t\ttestLabel: testValueA,\n\t\t}),\n\t}\n\trollout := generateRollout(\"test\", appName)\n\t// a versioned app matching robots testValueA\n\taddRobotToRollout(&rollout, testLabel, testValueA, newAppVersion)\n\t// Simply test that this works, and does throw an error.\n\t_, err := generateChartAssignments(al, robots[:], &rollout, nil)\n\tif err == nil {\n\t\tt.Fatal(\"Requesting a non-existant app should fail, but didn't\")\n\t}\n}\n\n// TestGenerateChartAssignments_vAppCloud tests a cloud cas\nfunc TestGenerateChartAssignments_vAppCloud(t *testing.T) {\n\tappName := \"myapp\"\n\tappVersion := \"17\"\n\ttestLabel := \"test-label\"\n\ttestValueA := \"test-value\"\n\tal := []apps.App{\n\t\t// Cloud does not have a Version field, and thus requires this\n\t\t// non versioned, canonical app.\n\t\tgenerateApp(appName, \"\", \"testpayload\", \"cloudpayload\"),\n\t\tgenerateApp(appName, appVersion, \"testpayload\", \"\"),\n\t}\n\trobots := []registry.Robot{\n\t\tgenerateRobot(\"robot1\", map[string]string{\n\t\t\ttestLabel: testValueA,\n\t\t}),\n\t}\n\trollout := generateRollout(\"test\", appName)\n\t// a versioned app matching robots testValueA\n\taddRobotToRollout(&rollout, testLabel, testValueA, appVersion)\n\t// expand to cover cloud\n\trollout.Spec.Cloud = apps.AppRolloutSpecCloud{}\n\t// Simply test that this works, and does throw an error.\n\tcas, err := generateChartAssignments(al, robots[:], &rollout, nil)\n\tif err != nil {\n\t\tt.Fatal(\"generate chart assignment with cloud rollout\")\n\t}\n\tif len(cas) != 2 {\n\t\tt.Fatalf(\"chart assingments, expected 2, got %d\", len(cas))\n\t}\n}\n\n// TestGenerateChartAssignments_vAppCloudMissing tests cloud rollout w/out App\n//\n// As the Cloud field is not versioned, it needs the canonical version of the\n// app to exist. In this test, we only create versioned Apps, and thus\n// the generation will fail.\nfunc TestGenerateChartAssignments_vAppCloudMissing(t *testing.T) {\n\tappName := \"myapp\"\n\tappVersion := \"17\"\n\tal := []apps.App{\n\t\t// only the versioned App will exist.\n\t\tgenerateApp(appName, appVersion, \"testpayload\", \"cloudpayload\"),\n\t}\n\trollout := generateRollout(\"test\", appName)\n\t// expand to cover cloud\n\trollout.Spec.Cloud = apps.AppRolloutSpecCloud{}\n\t// Simply test that this works, and does throw an error.\n\t_, err := generateChartAssignments(al, nil, &rollout, nil)\n\tif err != nil {\n\t\tt.Fatal(\"cloud rollout without unversioned app should pass, if rollout has no Cloud Values in Spec.\")\n\t}\n\trollout.Spec.Cloud = apps.AppRolloutSpecCloud{Values: apps.ConfigValues{\n\t\t\"test\": 1,\n\t}}\n\t// Simply test that this works, and does throw an error.\n\t_, err = generateChartAssignments(al, nil, &rollout, nil)\n\tif err != nil {\n\t\tt.Fatal(\"cloud rollout without unversioned app should pass, (and log) if rollout has Cloud Values in Spec.\")\n\t}\n}\n\nfunc TestGenerateChartAssignments_cloudPerRobot(t *testing.T) {\n\tvar app apps.App\n\tunmarshalYAML(t, &app, `\nmetadata:\n  name: foo\nspec:\n  components:\n    cloud:\n      inline: inline-cloud\n\t`)\n\n\tvar robots [2]registry.Robot\n\n\tunmarshalYAML(t, &robots[0], `\nmetadata:\n  name: robot1\n\t`)\n\tunmarshalYAML(t, &robots[1], `\nmetadata:\n  name: robot2\n  labels:\n    a: b\n\t`)\n\n\t// Rollout selects robot1, but not robot2.\n\tvar rollout apps.AppRollout\n\tunmarshalYAML(t, &rollout, `\nmetadata:\n  name: foo-rollout\n  namespace: default\nspec:\n  appName: foo\n  cloud:\n    values:\n      robots: should_be_overwritten\n  robots:\n  # robot1\n  - selector:\n      matchExpressions:\n      - {key: a, operator: DoesNotExist}\n\t`)\n\n\tvar expected apps.ChartAssignment\n\tunmarshalYAML(t, &expected, `\nmetadata:\n  name: foo-rollout-cloud\nspec:\n  clusterName: cloud\n  namespaceName: app-foo-rollout\n  chart:\n    inline: inline-cloud\n    values:\n      robots:\n      - name: robot1\n\t`)\n\n\tal := []apps.App{app}\n\tcas, err := generateChartAssignments(al, robots[:], &rollout, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Generate failed: %s\", err)\n\t}\n\tif len(cas) != 1 {\n\t\tt.Fatalf(\"Expected 1 ChartAssignments, got %d\", len(cas))\n\t}\n\tverifyChartAssignment(t, &expected, cas[0])\n}\n\nfunc TestGenerateChartAssignments_selectorOverlap(t *testing.T) {\n\tvar app apps.App\n\tunmarshalYAML(t, &app, `\nmetadata:\n  name: foo\nspec:\n  components:\n    robot:\n      inline: inline-robot\n\t`)\n\n\tvar robots [2]registry.Robot\n\tunmarshalYAML(t, &robots[0], `\nmetadata:\n  name: robot1\n\t`)\n\tunmarshalYAML(t, &robots[1], `\nmetadata:\n  name: robot2\n  labels:\n    a: b\n\t`)\n\n\t// Rollout with two selectors that match the same robot.\n\tvar rollout apps.AppRollout\n\tunmarshalYAML(t, &rollout, `\nmetadata:\n  name: foo-rollout\nspec:\n  appName: foo\n  robots:\n  - selector:\n      any: true\n  - selector:\n      matchLabels:\n        a: b\n\t`)\n\tal := []apps.App{app}\n\t_, err := generateChartAssignments(al, robots[:], &rollout, nil)\n\tif exp := errRobotSelectorOverlap(\"robot2\"); err != exp {\n\t\tt.Fatalf(\"expected error %q but got %q\", exp, err)\n\t}\n}\n\nfunc TestSetStatus(t *testing.T) {\n\tvar ca1, ca2, ca3 apps.ChartAssignment\n\tunmarshalYAML(t, &ca1, `\nmetadata:\n  name: ca1\nstatus:\n  phase: Failed\n\t`)\n\tunmarshalYAML(t, &ca2, `\nmetadata:\n  name: ca2\nstatus:\n  phase: Settled\n\t`)\n\tunmarshalYAML(t, &ca3, `\nmetadata:\n  name: ca3\nstatus:\n  phase: Ready\n\t`)\n\n\tvar ar apps.AppRollout\n\tsetStatus(&ar, 100, []apps.ChartAssignment{ca1, ca2, ca3})\n\n\tif ar.Status.Assignments != 100 {\n\t\tt.Errorf(\"Expected .status.assignments to be %d but got %d\", 100, ar.Status.Assignments)\n\t}\n\tif ar.Status.FailedAssignments != 1 {\n\t\tt.Errorf(\"Expected .status.failedAssignments to be %d but got %d\", 1, ar.Status.FailedAssignments)\n\t}\n\tif ar.Status.SettledAssignments != 2 {\n\t\tt.Errorf(\"Expected .status.settledAssignments to be %d but got %d\", 2, ar.Status.SettledAssignments)\n\t}\n\tif ar.Status.ReadyAssignments != 1 {\n\t\tt.Errorf(\"Expected .status.readyAssignments to be %d but got %d\", 1, ar.Status.ReadyAssignments)\n\t}\n\tif c := ar.Status.Conditions[0]; c.Type != apps.AppRolloutConditionSettled ||\n\t\tc.Status != core.ConditionFalse {\n\t\tt.Errorf(\"Unexpected first condition %v, expected Settled=False\", c)\n\t}\n\tif c := ar.Status.Conditions[1]; c.Type != apps.AppRolloutConditionReady ||\n\t\tc.Status != core.ConditionFalse {\n\t\tt.Errorf(\"Unexpected second condition %v, expected Ready=False\", c)\n\t}\n}\n\nfunc TestValidateAppRollout(t *testing.T) {\n\tcases := []struct {\n\t\tname       string\n\t\tcur        string\n\t\tshouldFail bool\n\t}{\n\t\t{\n\t\t\tname: \"valid-all\",\n\t\t\tcur: `\nspec:\n  appName: myapp\n  cloud:\n    values:\n      a: 2\n      b: {c: 3}\n  robots:\n  - selector:\n      any: true\n    values:\n      c: d\n  - selector:\n      matchLabels:\n        abc: def\n        foo: bar\n  - selector:\n      matchExpressions:\n      - {key: foo, Op: DoesExist}\n\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"valid-app-name-only\",\n\t\t\tcur: `\nspec:\n  appName: my-app.123\n\t`,\n\t\t},\n\t\t{\n\t\t\tname:       \"missing-app-name\",\n\t\t\tcur:        `spec: {}`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid-app-name\",\n\t\t\tcur: `\nspec:\n  appName: my%app\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"missing-robot-selector\",\n\t\t\tcur: `\nspec:\n  appName: myapp\n  robots:\n  - values:\n      a: b\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"wrong-selector\",\n\t\t\tcur: `\nspec:\n  appName: myapp\n  robots:\n  - selector:\n      a: b\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"cloud-values-Robots\",\n\t\t\tcur: `\nspec:\n  appName: myapp\n  cloud:\n    values:\n      robots:\n        c: d\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"robot-values-robot\",\n\t\t\tcur: `\nspec:\n  appName: myapp\n  robots:\n  - selector:\n      any: true\n    values:\n      robot:\n        c: d\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tvar cur apps.AppRollout\n\t\t\tunmarshalYAML(t, &cur, c.cur)\n\n\t\t\terr := appRolloutValidate(&cur)\n\t\t\tif err == nil && c.shouldFail {\n\t\t\t\tt.Fatal(\"expected failure but got none\")\n\t\t\t}\n\t\t\tif err != nil && !c.shouldFail {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestValidateApp tests all the labels and mechanism for the apps\nfunc TestValidateApp(t *testing.T) {\n\tcases := []struct {\n\t\tname       string\n\t\tcur        string\n\t\tshouldFail bool\n\t}{\n\t\t{\n\t\t\tname: \"valid\",\n\t\t\tcur: `\nmetadata:\n  name: app\n\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"valid-app-label-only\",\n\t\t\tcur: `\nmetadata:\n  name: app\n  labels:\n    cloudrobotics.com/app-name: app\n\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"valid-both-labels\",\n\t\t\tcur: `\nmetadata:\n  name: app.v17\n  labels:\n    cloudrobotics.com/app-name: app\n    cloudrobotics.com/app-version: 17\n\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"valid-both-labels-lower\",\n\t\t\tcur: `\nmetadata:\n  name: app.v17rc00\n  labels:\n    cloudrobotics.com/app-name: app\n    cloudrobotics.com/app-version: 17RC00\n\t`,\n\t\t},\n\t\t{\n\t\t\tname:       \"invalid-app-label-only\",\n\t\t\tshouldFail: true,\n\t\t\tcur: `\nmetadata:\n  name: app2\n  labels:\n    cloudrobotics.com/app-name: app\n\t`,\n\t\t},\n\t\t{\n\t\t\tname:       \"invalid-both-labels\",\n\t\t\tshouldFail: true,\n\t\t\tcur: `\nmetadata:\n  name: app.v17rc10\n  labels:\n    cloudrobotics.com/app-name: app\n    cloudrobotics.com/app-version: 17RC00\n\t`,\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tvar cur apps.App\n\t\t\tunmarshalYAML(t, &cur, c.cur)\n\n\t\t\terr := appValidate(&cur)\n\t\t\tif err == nil && c.shouldFail {\n\t\t\t\tt.Fatal(\"expected failure but got none\")\n\t\t\t}\n\t\t\tif err != nil && !c.shouldFail {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/controller/chartassignment/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\", \"gomock\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"controller.go\",\n        \"release.go\",\n        \"validator.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/controller/chartassignment\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/gcr:go_default_library\",\n        \"//src/go/pkg/synk:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/validation:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library\",\n        \"@io_k8s_cli_runtime//pkg/resource:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//tools/record:go_default_library\",\n        \"@io_k8s_client_go//util/workqueue:go_default_library\",\n        \"@io_k8s_helm//pkg/chartutil:go_default_library\",\n        \"@io_k8s_helm//pkg/downloader:go_default_library\",\n        \"@io_k8s_helm//pkg/getter:go_default_library\",\n        \"@io_k8s_helm//pkg/helm/helmpath:go_default_library\",\n        \"@io_k8s_helm//pkg/proto/hapi/chart:go_default_library\",\n        \"@io_k8s_helm//pkg/renderutil:go_default_library\",\n        \"@io_k8s_helm//pkg/repo:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/controller:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/event:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/handler:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/manager:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/reconcile:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/source:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/webhook/admission:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n    ],\n)\n\ngomock(\n    name = \"synk_interface\",\n    out = \"synk_interface_test.go\",\n    interfaces = [\"Interface\"],\n    library = \"//src/go/pkg/synk:go_default_library\",\n    package = \"chartassignment\",\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\n        \"release_test.go\",\n        \"synk_interface_test.go\",\n        \"validator_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/kubetest:go_default_library\",\n        \"@com_github_golang_mock//gomock:go_default_library\",\n        \"@io_k8s_client_go//tools/record:go_default_library\",\n        \"@io_k8s_helm//pkg/chartutil:go_default_library\",\n        \"@io_k8s_sigs_yaml//:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/controller/chartassignment/controller.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage chartassignment\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/gcr\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\tcore \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/client-go/util/workqueue\"\n\tkclient \"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n)\n\nconst (\n\t// Allow the Service Account Controller some time to create the default\n\t// SA in a new namespace.\n\tdefaultServiceAccountDeadline = time.Minute\n\tfieldIndexNamespace           = \"spec.namespaceName\"\n\tstatusCheckingOptOutLabel     = \"cloudrobotics.com/opt-out-error-checking\"\n)\n\n// Add adds a controller and validation webhook for the ChartAssignment resource type\n// to the manager and server.\n// Handled ChartAssignments are filtered by the provided cluster.\nfunc Add(ctx context.Context, mgr manager.Manager, cloud bool) error {\n\tr := &Reconciler{\n\t\tkube:     mgr.GetClient(),\n\t\trecorder: mgr.GetEventRecorderFor(\"chartassignment-controller\"),\n\t\tcloud:    cloud,\n\t}\n\tvar err error\n\tr.releases, err = newReleases(mgr.GetConfig(), r.recorder)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc, err := controller.New(\"chartassignment\", mgr, controller.Options{\n\t\tReconciler: r,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = mgr.GetCache().IndexField(ctx, &apps.ChartAssignment{}, fieldIndexNamespace,\n\t\tfunc(o kclient.Object) []string {\n\t\t\treturn []string{o.(*apps.ChartAssignment).Spec.NamespaceName}\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"add field indexer\")\n\t}\n\terr = c.Watch(\n\t\tsource.Kind(mgr.GetCache(), &apps.ChartAssignment{}),\n\t\t&handler.EnqueueRequestForObject{},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = c.Watch(\n\t\tsource.Kind(mgr.GetCache(), &core.Pod{}),\n\t\t&handler.Funcs{\n\t\t\tCreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tr.enqueueForPod(ctx, e.Object, q)\n\t\t\t},\n\t\t\tUpdateFunc: func(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tr.enqueueForPod(ctx, e.ObjectNew, q)\n\t\t\t},\n\t\t\tDeleteFunc: func(ctx context.Context, e event.DeleteEvent, q workqueue.RateLimitingInterface) {\n\t\t\t\tr.enqueueForPod(ctx, e.Object, q)\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"watch Apps\")\n\t}\n\treturn nil\n}\n\nfunc (r *Reconciler) enqueueForPod(ctx context.Context, m meta.Object, q workqueue.RateLimitingInterface) {\n\tvar cas apps.ChartAssignmentList\n\terr := r.kube.List(ctx, &cas, kclient.MatchingFields(map[string]string{fieldIndexNamespace: m.GetNamespace()}))\n\tif err != nil {\n\t\tslog.Error(\"List ChartAssignments failed\", slog.String(\"Namespace\", m.GetNamespace()), ilog.Err(err))\n\t\treturn\n\t}\n\tfor _, ca := range cas.Items {\n\t\tq.Add(reconcile.Request{\n\t\t\tNamespacedName: kclient.ObjectKey{Name: ca.Name},\n\t\t})\n\t}\n}\n\n// Reconciler provides an idempotent function that brings the cluster into a\n// state consistent with the specification of a ChartAssignment.\ntype Reconciler struct {\n\tkube     kclient.Client\n\trecorder record.EventRecorder\n\treleases *releases\n\tcloud    bool\n}\n\n// Reconcile creates and updates a Synk ResourceSet for the given chart\n// assignment. It rolls back releases to the previous revision if an upgrade\n// failed. It continuously requeues the ChartAssignment for reconciliation to\n// monitor the status of the ResourceSet.\nfunc (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {\n\tvar as apps.ChartAssignment\n\terr := r.kube.Get(ctx, req.NamespacedName, &as)\n\n\tif k8serrors.IsNotFound(err) {\n\t\t// Assignment was already deleted. We did all required cleanup\n\t\t// when removing the finalizer. Thus, there's nothing to do.\n\t\tslog.Info(\"ChartAssignment no longer exists, skipping reconciliation...\", slog.Any(\"Name\", req.NamespacedName))\n\t\treturn reconcile.Result{}, nil\n\t} else if err != nil {\n\t\treturn reconcile.Result{}, fmt.Errorf(\"getting ChartAssignment %q failed: %s\", req, err)\n\t}\n\t// Reconcile no ChartAssignments for robots on the cloud.\n\t// We do have ChartAssignments without the robot label on the robot but they\n\t// do not pass through the cloud cluster.\n\t// Labels is of type map[string]string but we care only for the existence\n\t// of the key.\n\t_, isRobot := as.Labels[\"cloudrobotics.com/robot-name\"]\n\tif r.cloud && isRobot {\n\t\treturn reconcile.Result{}, nil\n\t}\n\treturn r.reconcile(ctx, &as)\n}\n\nconst (\n\t// The finalizer that's applied to assignments to block their garbage collection\n\t// until the Synk ResourceSet is deleted.\n\tfinalizer = \"helm.apps.cloudrobotics.com\"\n\t// Requeue interval when the underlying Synk ResourceSet is not in a stable state yet.\n\trequeueFast = 3 * time.Second\n\t// Requeue interval after the underlying Synk ResourceSete reached a stable state.\n\trequeueSlow = 3 * time.Minute\n)\n\n// namespaceDeletionError indicates that a namespace could not be created\n// because a previously-created namespace with the same name is pending\n// deletion. This occurs when you delete and recreate a chartassignment. It is\n// transient, but may last seconds or minutes if the namespace contains\n// resources that are slow to delete.\ntype namespaceDeletionError struct {\n\tmsg string\n}\n\nfunc (e *namespaceDeletionError) Error() string { return e.msg }\n\n// missingServiceAccountError indicates that the default ServiceAccount has not\n// yet been created, and that the chart should not be updated to avoid creating\n// pods before the ImagePullSecrets have been applied.\ntype missingServiceAccountError struct {\n\tmsg string\n}\n\nfunc (e *missingServiceAccountError) Error() string { return e.msg }\n\nfunc (r *Reconciler) ensureNamespace(ctx context.Context, as *apps.ChartAssignment) (*core.Namespace, error) {\n\t// Create application namespace if it doesn't exist.\n\tvar ns core.Namespace\n\terr := r.kube.Get(ctx, kclient.ObjectKey{Name: as.Spec.NamespaceName}, &ns)\n\n\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\treturn nil, fmt.Errorf(\"getting Namespace %q failed: %s\", as.Spec.NamespaceName, err)\n\t}\n\tif ns.DeletionTimestamp != nil {\n\t\treturn nil, &namespaceDeletionError{\n\t\t\tmsg: fmt.Sprintf(\"namespace %q was marked for deletion at %s, skipping\", as.Spec.NamespaceName, ns.DeletionTimestamp),\n\t\t}\n\t}\n\n\tcreateNamespace := k8serrors.IsNotFound(err)\n\tns.Name = as.Spec.NamespaceName\n\tns.Labels = map[string]string{\"app\": as.Name}\n\n\t// Add ourselves to the owners if we aren't already.\n\t_true := true\n\tadded := setOwnerReference(&ns.ObjectMeta, meta.OwnerReference{\n\t\tAPIVersion:         as.APIVersion,\n\t\tKind:               as.Kind,\n\t\tName:               as.Name,\n\t\tUID:                as.UID,\n\t\tBlockOwnerDeletion: &_true,\n\t})\n\tif !added {\n\t\treturn &ns, nil\n\t}\n\tif createNamespace {\n\t\treturn &ns, r.kube.Create(ctx, &ns)\n\t}\n\treturn &ns, r.kube.Update(ctx, &ns)\n}\n\n// ensureSecrets copies secrets from the default namespace, since service\n// accounts and pods cannot reference secrets in other namespaces.\nfunc (r *Reconciler) ensureSecrets(ctx context.Context, as *apps.ChartAssignment) error {\n\tvar secrets core.SecretList\n\terr := r.kube.List(ctx, &secrets, kclient.MatchingLabels(map[string]string{\n\t\t\"cloudrobotics.com/copy-to-chart-namespaces\": \"true\",\n\t}))\n\tfor _, secret := range secrets.Items {\n\t\t// Drop the resourceVersion/uid/etc from the copied resource, to avoid\n\t\t// confusing the apiserver, and drop annotations/labels that aren't\n\t\t// needed. We just need the name and the new namespace.\n\t\tsecret.ObjectMeta = meta.ObjectMeta{\n\t\t\tNamespace: as.Spec.NamespaceName,\n\t\t\tName:      secret.Name,\n\t\t}\n\t\terr = r.kube.Create(ctx, &secret)\n\t\tif k8serrors.IsAlreadyExists(err) {\n\t\t\treturn nil\n\t\t} else if err != nil {\n\t\t\treturn fmt.Errorf(\"create Secret %s/%s: %w\", as.Spec.NamespaceName, secret.Name, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureServiceAccount makes sure we have an image pull secret for gcr.io inside the apps namespace\n// and the default service account configured to use it. This is needed to make apps work that\n// reference images from a private container registry.\n// TODO(ensonic): Put this behind a flag to only do this as needed.\nfunc (r *Reconciler) ensureServiceAccount(ctx context.Context, ns *core.Namespace, as *apps.ChartAssignment) error {\n\tif r.cloud {\n\t\t// We don't need any of this for cloud charts.\n\t\treturn nil\n\t}\n\n\t// Check for the image pull secret that the SA will refer to.\n\tvar secret core.Secret\n\terr := r.kube.Get(ctx, kclient.ObjectKey{Namespace: as.Spec.NamespaceName, Name: gcr.SecretName}, &secret)\n\tif k8serrors.IsNotFound(err) {\n\t\treturn nil\n\t}\n\n\t// Configure the default service account in the namespace.\n\tvar sa core.ServiceAccount\n\terr = r.kube.Get(ctx, kclient.ObjectKey{Namespace: as.Spec.NamespaceName, Name: \"default\"}, &sa)\n\tif err != nil {\n\t\tif k8serrors.IsNotFound(err) && time.Since(ns.CreationTimestamp.Time) < defaultServiceAccountDeadline {\n\t\t\t// The Service Account Controller hasn't created the default SA yet.\n\t\t\treturn &missingServiceAccountError{\n\t\t\t\tmsg: fmt.Sprintf(\"ServiceAccount \\\"%s:default\\\" not yet created\", ns.Name),\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"getting ServiceAccount \\\"%s:default\\\" failed: %s\", as.Spec.NamespaceName, err)\n\t}\n\n\t// Only add the secret once.\n\tips := core.LocalObjectReference{Name: gcr.SecretName}\n\tfound := false\n\tfor _, s := range sa.ImagePullSecrets {\n\t\tif s == ips {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\tsa.ImagePullSecrets = append(sa.ImagePullSecrets, ips)\n\t}\n\treturn r.kube.Update(ctx, &sa)\n}\n\nfunc (r *Reconciler) reconcile(ctx context.Context, as *apps.ChartAssignment) (reconcile.Result, error) {\n\t// If we are scheduled for deletion, delete the Synk ResourceSet and drop our\n\t// finalizer so garbage collection can continue.\n\tif as.DeletionTimestamp != nil {\n\t\tslog.Info(\"Ensure ChartAssignment cleanup\", slog.String(\"Name\", as.Name))\n\n\t\tif err := r.ensureDeleted(ctx, as); err != nil {\n\t\t\treturn reconcile.Result{}, fmt.Errorf(\"ensure deleted: %s\", err)\n\t\t}\n\t\tif err := r.setStatus(ctx, as); err != nil {\n\t\t\treturn reconcile.Result{}, fmt.Errorf(\"set status: %s\", err)\n\t\t}\n\t\t// Requeue to track deletion progress.\n\t\treturn reconcile.Result{Requeue: true, RequeueAfter: requeueFast}, nil\n\t}\n\n\tns, err := r.ensureNamespace(ctx, as)\n\tif err != nil {\n\t\tif _, ok := err.(*namespaceDeletionError); ok {\n\t\t\tslog.Error(\"Ensure namespace\", ilog.Err(err))\n\t\t\t// Requeue to track deletion progress.\n\t\t\treturn reconcile.Result{Requeue: true, RequeueAfter: requeueFast}, nil\n\t\t}\n\t\treturn reconcile.Result{}, fmt.Errorf(\"ensure namespace: %s\", err)\n\t}\n\tif err := r.ensureSecrets(ctx, as); err != nil {\n\t\treturn reconcile.Result{}, fmt.Errorf(\"ensure secrets: %s\", err)\n\t}\n\tif err := r.ensureServiceAccount(ctx, ns, as); err != nil {\n\t\tif _, ok := err.(*missingServiceAccountError); ok {\n\t\t\tslog.Warn(\"Reconcile failed. This is expected to occur rarely.\", ilog.Err(err))\n\t\t\treturn reconcile.Result{Requeue: true, RequeueAfter: requeueFast}, nil\n\t\t} else {\n\t\t\treturn reconcile.Result{}, fmt.Errorf(\"ensure service-account: %s\", err)\n\t\t}\n\t}\n\t// Ensure a finalizer on the ChartAssignment so we don't get deleted before\n\t// we've properly deleted the associated Synk ResourceSet.\n\tif !stringsContain(as.Finalizers, finalizer) {\n\t\tas.Finalizers = append(as.Finalizers, finalizer)\n\t\tif err := r.kube.Update(ctx, as); err != nil {\n\t\t\treturn reconcile.Result{}, errors.Wrap(err, \"add finalizer\")\n\t\t}\n\t}\n\n\tr.releases.ensureUpdated(as)\n\n\tif err := r.setStatus(ctx, as); err != nil {\n\t\tif k8serrors.IsConflict(err) {\n\t\t\t// The cache has an old status. This can be ignored, as\n\t\t\t// controller-runtime will reconcile again when the cache updates:\n\t\t\t// https://github.com/kubernetes-sigs/controller-runtime/issues/377\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\treturn reconcile.Result{}, errors.Wrap(err, \"update status\")\n\t}\n\t// Quickly requeue for status updates when deployment is in progress.\n\tswitch as.Status.Phase {\n\tcase apps.ChartAssignmentPhaseReady, apps.ChartAssignmentPhaseFailed:\n\t\treturn reconcile.Result{Requeue: true, RequeueAfter: requeueSlow}, nil\n\t}\n\treturn reconcile.Result{Requeue: true, RequeueAfter: requeueFast}, nil\n\n}\n\nfunc condition(b bool) core.ConditionStatus {\n\tif b {\n\t\treturn core.ConditionTrue\n\t}\n\treturn core.ConditionFalse\n}\n\nfunc (r *Reconciler) setStatus(ctx context.Context, as *apps.ChartAssignment) error {\n\tstatus, ok := r.releases.status(as.Name)\n\tif !ok {\n\t\treturn nil\n\t} else if status.phase == apps.ChartAssignmentPhaseDeleted {\n\t\t// The assignment may have been garbage collected already, so\n\t\t// don't try to update the status.\n\t\treturn nil\n\t}\n\n\tas.Status.ObservedGeneration = as.Generation\n\tas.Status.Phase = status.phase\n\n\tif c := condition(status.phase == apps.ChartAssignmentPhaseSettled); status.err == nil {\n\t\tsetCondition(as, apps.ChartAssignmentConditionSettled, c, \"\")\n\t} else {\n\t\tsetCondition(as, apps.ChartAssignmentConditionSettled, c, status.err.Error())\n\t}\n\n\tvar ns core.Namespace\n\tif err := r.kube.Get(ctx, kclient.ObjectKey{Name: as.Spec.NamespaceName}, &ns); err != nil {\n\t\tif k8serrors.IsNotFound(err) {\n\t\t\tsetCondition(as, apps.ChartAssignmentConditionReady, condition(false),\n\t\t\t\t\"waiting for namespace creation\")\n\t\t} else {\n\t\t\treturn errors.Wrap(err, \"get namespace\")\n\t\t}\n\t} else {\n\t\t// Determine readiness based on pods in the app namespace being ready.\n\t\t// This is an incomplete heuristic but it should catch the vast majority of errors.\n\t\tvar pods core.PodList\n\t\t// Note, this return 0 is the namespace has not been created!\n\t\tif err := r.kube.List(ctx, &pods, kclient.InNamespace(as.Spec.NamespaceName)); err != nil {\n\t\t\treturn errors.Wrap(err, \"list pods\")\n\t\t}\n\n\t\t// Omit pods that have opted out of status checking.\n\t\tvar filteredPods []core.Pod\n\t\tfor _, pod := range pods.Items {\n\t\t\tif val, exists := pod.Labels[statusCheckingOptOutLabel]; !exists || val != \"true\" {\n\t\t\t\tfilteredPods = append(filteredPods, pod)\n\t\t\t}\n\t\t}\n\t\tready, total := 0, len(filteredPods)\n\n\t\tfor _, p := range filteredPods {\n\t\t\tswitch p.Status.Phase {\n\t\t\tcase core.PodRunning, core.PodSucceeded:\n\t\t\t\tready++\n\t\t\t}\n\t\t}\n\t\t// Readiness is only given if the release is settled to begin with.\n\t\tif status.phase != apps.ChartAssignmentPhaseSettled {\n\t\t\tsetCondition(as, apps.ChartAssignmentConditionReady, core.ConditionFalse,\n\t\t\t\t\"Release not settled yet\")\n\t\t} else {\n\t\t\tif ready == total {\n\t\t\t\tas.Status.Phase = apps.ChartAssignmentPhaseReady\n\t\t\t}\n\t\t\tsetCondition(as, apps.ChartAssignmentConditionReady, condition(ready == total),\n\t\t\t\tfmt.Sprintf(\"%d/%d pods are running or succeeded\", ready, total))\n\t\t}\n\t}\n\treturn r.kube.Status().Update(ctx, as)\n}\n\n// ensureDeleted ensures that the Synk ResourceSet is deleted and the finalizer gets removed.\nfunc (r *Reconciler) ensureDeleted(ctx context.Context, as *apps.ChartAssignment) error {\n\tr.releases.ensureDeleted(as)\n\tstatus, ok := r.releases.status(as.Name)\n\tif !ok {\n\t\treturn fmt.Errorf(\"release status not found\")\n\t}\n\n\tif status.phase != apps.ChartAssignmentPhaseDeleted {\n\t\t// Deletion still in progress, check again later.\n\t\treturn nil\n\t}\n\tif !stringsContain(as.Finalizers, finalizer) {\n\t\treturn nil\n\t}\n\tas.Finalizers = stringsDelete(as.Finalizers, finalizer)\n\tif err := r.kube.Update(ctx, as); err != nil {\n\t\treturn fmt.Errorf(\"update failed: %s\", err)\n\t}\n\treturn nil\n}\n\nfunc stringsContain(list []string, s string) bool {\n\tfor _, x := range list {\n\t\tif x == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc stringsDelete(list []string, s string) (res []string) {\n\tfor _, x := range list {\n\t\tif x != s {\n\t\t\tres = append(res, x)\n\t\t}\n\t}\n\treturn res\n}\n\n// setOwnerReference ensures the owner reference is set and returns true if it did\n// not exist before. Existing references are detected based on the UID field.\nfunc setOwnerReference(om *meta.ObjectMeta, ref meta.OwnerReference) bool {\n\tfor i, or := range om.OwnerReferences {\n\t\tif ref.UID == or.UID {\n\t\t\tom.OwnerReferences[i] = ref\n\t\t\treturn false\n\t\t}\n\t}\n\tom.OwnerReferences = append(om.OwnerReferences, ref)\n\treturn true\n}\n\n// inCondition returns true if the ChartAssignment has a condition of the given\n// type in state true.\nfunc inCondition(as *apps.ChartAssignment, c apps.ChartAssignmentConditionType) bool {\n\tfor _, cond := range as.Status.Conditions {\n\t\tif cond.Type == c && cond.Status == core.ConditionTrue {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// setCondition adds or updates a condition. Existing conditions are detected\n// based on the Type field.\nfunc setCondition(as *apps.ChartAssignment, t apps.ChartAssignmentConditionType, v core.ConditionStatus, msg string) {\n\tnow := meta.Now()\n\n\tfor i, c := range as.Status.Conditions {\n\t\tif c.Type != t {\n\t\t\tcontinue\n\t\t}\n\t\t// Update existing condition.\n\t\tif c.Status != v || c.Message != msg {\n\t\t\tc.LastUpdateTime = now\n\t\t}\n\t\tif c.Status != v {\n\t\t\tc.LastTransitionTime = now\n\t\t}\n\t\tc.Message = msg\n\t\tc.Status = v\n\t\tas.Status.Conditions[i] = c\n\t\treturn\n\t}\n\t// Condition set for the first time.\n\tas.Status.Conditions = append(as.Status.Conditions, apps.ChartAssignmentCondition{\n\t\tType:               t,\n\t\tLastUpdateTime:     now,\n\t\tLastTransitionTime: now,\n\t\tStatus:             v,\n\t\tMessage:            msg,\n\t})\n}\n"
  },
  {
    "path": "src/go/pkg/controller/chartassignment/release.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage chartassignment\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/synk\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\t\"go.opencensus.io/trace\"\n\tcore \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/cli-runtime/pkg/resource\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/helm/pkg/chartutil\"\n\t\"k8s.io/helm/pkg/downloader\"\n\t\"k8s.io/helm/pkg/getter\"\n\t\"k8s.io/helm/pkg/helm/helmpath\"\n\t\"k8s.io/helm/pkg/proto/hapi/chart\"\n\t\"k8s.io/helm/pkg/renderutil\"\n\t\"k8s.io/helm/pkg/repo\"\n)\n\n// releases is a cache of releases currently handled.\ntype releases struct {\n\trecorder record.EventRecorder\n\tsynk     synk.Interface\n\n\tmtx sync.Mutex\n\tm   map[string]*release\n}\n\nfunc newReleases(cfg *rest.Config, rec record.EventRecorder) (*releases, error) {\n\tsynk, err := synk.NewForConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &releases{\n\t\trecorder: rec,\n\t\tm:        map[string]*release{},\n\t\tsynk:     synk,\n\t}, nil\n}\n\n// release is a cache object which acts as a proxy for Synk ResourceSets.\ntype release struct {\n\tname     string\n\tsynk     synk.Interface\n\trecorder record.EventRecorder\n\tactorc   chan func()\n\tgen      int64 // last deployed generation.\n\n\tmtx    sync.Mutex\n\tstatus releaseStatus\n}\n\ntype releaseStatus struct {\n\tphase apps.ChartAssignmentPhase\n\terr   error // last encountered error\n\tretry bool  // whether deployment should be retried.\n}\n\n// status returns the current phase and error of the release. ok is false\n// if the release does not exist in the cache.\nfunc (rs *releases) status(name string) (releaseStatus, bool) {\n\trs.mtx.Lock()\n\tr, ok := rs.m[name]\n\trs.mtx.Unlock()\n\tif !ok {\n\t\treturn releaseStatus{}, false\n\t}\n\tr.mtx.Lock()\n\tdefer r.mtx.Unlock()\n\treturn r.status, true\n}\n\n// add a release to the cache with an initial phase.\nfunc (rs *releases) add(name string) *release {\n\trs.mtx.Lock()\n\tdefer rs.mtx.Unlock()\n\n\tr, ok := rs.m[name]\n\tif ok {\n\t\treturn r\n\t}\n\tr = &release{\n\t\tname:     name,\n\t\tsynk:     rs.synk,\n\t\trecorder: rs.recorder,\n\t\tactorc:   make(chan func()),\n\t}\n\tr.status.phase = apps.ChartAssignmentPhaseAccepted\n\trs.m[name] = r\n\t// Start applying updates in the background.\n\tgo r.run()\n\treturn r\n}\n\n// ensureUpdated ensures that the ChartAssignment is installed as a Synk\n// ResourceSet.\n// It returns true if it could initiate an update successfully.\nfunc (rs *releases) ensureUpdated(as *apps.ChartAssignment) bool {\n\tr := rs.add(as.Name)\n\tstatus, _ := rs.status(as.Name)\n\n\t// If the last generation we deployed matches the provided one, there's\n\t// nothing to do. Unless the previous update set the retry flag due to\n\t// a transient error.\n\t// For a fresh release object, a first update will always happen as\n\t// r.generation is 0 and resource generations start at 1.\n\tif r.generation() == as.Generation && !status.retry {\n\t\treturn true\n\t}\n\t// update() starts by loading the chart. Set this before returning so the\n\t// caller sees the right phase.\n\tr.setPhase(apps.ChartAssignmentPhaseLoadingChart)\n\tasCopy := as.DeepCopy()\n\tstarted := r.start(func() { r.update(asCopy) })\n\tif started {\n\t\tr.setGeneration(as.Generation)\n\t}\n\treturn started\n}\n\n// ensureDeleted ensures that deletion of the release is run.\n// It returns true if it could initiate deletion successfully.\nfunc (rs *releases) ensureDeleted(as *apps.ChartAssignment) bool {\n\tr := rs.add(as.Name)\n\tasCopy := as.DeepCopy()\n\treturn r.start(func() { r.delete(asCopy) })\n}\n\n// run all functions sent on the actor channel in sequence.\nfunc (r *release) run() {\n\tfor f := range r.actorc {\n\t\tf()\n\t}\n}\n\n// start tries to launch f on the worker goroutine.\n// If there's already a function running, it immediately returns false.\nfunc (r *release) start(f func()) bool {\n\tselect {\n\tcase r.actorc <- f:\n\t\treturn true\n\tdefault:\n\t}\n\treturn false\n}\n\nfunc (r *release) setPhase(p apps.ChartAssignmentPhase) {\n\tr.mtx.Lock()\n\tr.status.phase = p\n\tr.status.err = nil\n\tr.status.retry = false\n\tr.mtx.Unlock()\n}\n\nfunc (r *release) generation() int64 {\n\tr.mtx.Lock()\n\tdefer r.mtx.Unlock()\n\treturn r.gen\n}\n\nfunc (r *release) setGeneration(generation int64) {\n\tr.mtx.Lock()\n\tr.gen = generation\n\tr.mtx.Unlock()\n}\n\nfunc (r *release) setFailed(err error, retry bool) {\n\tr.mtx.Lock()\n\tif !retry {\n\t\tslog.Warn(\"chart failed\", slog.Any(\"phase\", r.status.phase), ilog.Err(err))\n\t\t// We only update the phase for non-retriable errors. This mitigates a\n\t\t// race condition between ensureUpdated, which sets phase=Updating when\n\t\t// retrying, and setStatus, which reads either the old phase or Updating\n\t\t// and copies it to the chartassignment status.\n\t\tr.status.phase = apps.ChartAssignmentPhaseFailed\n\t} else {\n\t\tslog.Warn(\"chart failed (retrying)\", slog.Any(\"phase\", r.status.phase), ilog.Err(err))\n\t}\n\tr.status.err = err\n\tr.status.retry = retry\n\tr.mtx.Unlock()\n}\n\nfunc (r *release) delete(as *apps.ChartAssignment) {\n\tr.mtx.Lock()\n\tcurrentPhase := r.status.phase\n\tr.mtx.Unlock()\n\tif currentPhase == apps.ChartAssignmentPhaseDeleted {\n\t\treturn\n\t}\n\n\tr.setPhase(apps.ChartAssignmentPhaseDeleting)\n\tr.recorder.Event(as, core.EventTypeNormal, \"DeleteChart\", \"deleting chart\")\n\n\tif err := r.synk.Delete(context.Background(), as.Name); err != nil {\n\t\tr.recorder.Event(as, core.EventTypeWarning, \"Failure\", err.Error())\n\t\tr.setFailed(errors.Wrap(err, \"delete release\"), synk.IsTransientErr(err))\n\t}\n\tr.recorder.Event(as, core.EventTypeNormal, \"Success\", \"chart deleted successfully\")\n\tr.setPhase(apps.ChartAssignmentPhaseDeleted)\n\t// Reset last deployed generation to 0 as the ChartAssignment will be deleted\n\t// and its generation start at 1 again if it is re-created.\n\tr.setGeneration(0)\n}\n\nfunc (r *release) update(as *apps.ChartAssignment) {\n\tresources, retry, err := loadAndExpandChart(as)\n\tif err != nil {\n\t\tr.recorder.Event(as, core.EventTypeWarning, \"Failure\", err.Error())\n\t\tr.setFailed(err, retry)\n\t\treturn\n\t}\n\n\tr.setPhase(apps.ChartAssignmentPhaseUpdating)\n\tr.recorder.Event(as, core.EventTypeNormal, \"UpdateChart\", \"update chart\")\n\n\topts := &synk.ApplyOptions{\n\t\tNamespace:        as.Spec.NamespaceName,\n\t\tEnforceNamespace: true,\n\t\tLog: func(r *unstructured.Unstructured, action apps.ResourceAction, status, msg string) {\n\t\t\tif status == synk.StatusSuccess {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Resource is meant to be human-readable\n\t\t\t// We should not use 'Message' as key to prevent collisions between the\n\t\t\t// log message and its arguments.\n\t\t\tslog.Warn(\"Error applying resource\",\n\t\t\t\tslog.String(\"Status\", strings.ToUpper(status)),\n\t\t\t\tslog.String(\"Action\", string(action)),\n\t\t\t\tslog.String(\"Resource\", fmt.Sprintf(\"%s/%s %s\", r.GetAPIVersion(), r.GetKind(), r.GetName())),\n\t\t\t\tslog.String(\"Note\", msg))\n\t\t},\n\t}\n\tspanContext := trace.SpanContext{}\n\tif tid, found := as.GetAnnotations()[\"cloudrobotics.com/trace-id\"]; found {\n\t\tif _, err := hex.Decode(spanContext.TraceID[:], []byte(tid)); err != nil {\n\t\t\tslog.Error(\"decoding TraceID\", slog.String(\"TraceID\", tid), ilog.Err(err))\n\t\t}\n\t}\n\tctx, span := trace.StartSpanWithRemoteParent(context.Background(), \"Apply \"+as.Name, spanContext)\n\t_, err = r.synk.Apply(ctx, as.Name, opts, resources...)\n\tspan.End()\n\tif err != nil {\n\t\tr.recorder.Event(as, core.EventTypeWarning, \"Failure\", err.Error())\n\t\tr.setFailed(err, synk.IsTransientErr(err))\n\t\treturn\n\t}\n\tr.recorder.Event(as, core.EventTypeNormal, \"Success\", \"chart updated successfully\")\n\tr.setPhase(apps.ChartAssignmentPhaseSettled)\n}\n\nfunc loadAndExpandChart(as *apps.ChartAssignment) ([]*unstructured.Unstructured, bool, error) {\n\tc, values, err := loadChart(&as.Spec.Chart)\n\tif err != nil {\n\t\treturn nil, true, err\n\t}\n\t// Expand chart.\n\tmanifests, err := renderutil.Render(c, &chart.Config{Raw: values}, renderutil.Options{\n\t\tReleaseOptions: chartutil.ReleaseOptions{\n\t\t\tName:      as.Name,\n\t\t\tNamespace: as.Spec.NamespaceName,\n\t\t\tIsInstall: true,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, false, errors.Wrap(err, \"render chart\")\n\t}\n\t// TODO: consider giving the synk package first-class support for raw manifests\n\t// so that their decoding errors are fully surfaced in the ResourceSet. Otherwise,\n\t// common YAML errors will only be surfaced one-by-one, which is tedious to handle.\n\tres, err := decodeManifests(manifests)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\treturn res, false, nil\n}\n\nfunc loadChart(cspec *apps.AssignedChart) (*chart.Chart, string, error) {\n\tvar archive io.Reader\n\tvar err error\n\n\tif cspec.Inline != \"\" {\n\t\tarchive = base64.NewDecoder(base64.StdEncoding, strings.NewReader(cspec.Inline))\n\t} else {\n\t\tarchive, err = fetchChartTar(cspec.Repository, cspec.Name, cspec.Version)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", errors.Wrap(err, \"retrieve chart\")\n\t\t}\n\t}\n\tc, err := chartutil.LoadArchive(archive)\n\tif err != nil {\n\t\treturn nil, \"\", errors.Wrap(err, \"load chart archive\")\n\t}\n\n\t// Ensure charts in requirements.yaml are actually in packaged in.\n\tif req, err := chartutil.LoadRequirements(c); err == nil {\n\t\tif err := renderutil.CheckDependencies(c, req); err != nil {\n\t\t\treturn nil, \"\", errors.Wrap(err, \"check chart dependencies\")\n\t\t}\n\t} else if err != chartutil.ErrRequirementsNotFound {\n\t\treturn nil, \"\", errors.Wrap(err, \"load chart requirements\")\n\t}\n\n\t// TODO: handle empty c.Values, cspec.Values\n\t// Build the full set of values including the default ones. Even though\n\t// they are part of the chart, they are ignored if we don't provide\n\t// them explicitly.\n\tvals, err := chartutil.ReadValues([]byte(c.Values.Raw))\n\tif err != nil {\n\t\treturn nil, \"\", errors.Wrap(err, \"reading chart values\")\n\t}\n\tvals.MergeInto(chartutil.Values(cspec.Values)) // ChartAssignment values.\n\n\tvalsRaw, err := vals.YAML()\n\tif err != nil {\n\t\treturn nil, \"\", errors.Wrap(err, \"encode values\")\n\t}\n\treturn c, valsRaw, nil\n}\n\nfunc fetchChartTar(repoURL, name, version string) (io.Reader, error) {\n\tc := downloader.ChartDownloader{\n\t\tGetters: getter.Providers{\n\t\t\t{Schemes: []string{\"http\", \"https\"}, New: newHTTPGetter},\n\t\t},\n\t\tHelmHome: helmpath.Home(os.ExpandEnv(\"$HOME/.helm\")),\n\t\tOut:      os.Stderr,\n\t\tKeyring:  os.ExpandEnv(\"$HOME/.gnupg/pubring.gpg\"),\n\t\tVerify:   downloader.VerifyIfPossible,\n\t}\n\n\tdir, err := os.MkdirTemp(\"\", name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer os.RemoveAll(dir)\n\n\t// This is only called when we fetch by repo URL rather than simply\n\t// by e.g. stable/postgresql.\n\tchartURL, err := repo.FindChartInRepoURL(repoURL, name, version, \"\", \"\", \"\", c.Getters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// NOTE(fabxc): Since we provide a full chartURL, DownloadTo will pull from exactly\n\t// that URL. It will however check for repos in $HOME/.helm to determine\n\t// whether it should do this with special certificates for that domain.\n\t// (The same cert files we left blank above.)\n\t// We might just want to implement this ourselves once we know what auth\n\t// strategies we want to support and how users can configure them.\n\tfilename, _, err := c.DownloadTo(chartURL, version, dir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tb, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bytes.NewReader(b), nil\n}\n\n// newHTTPGetter return a Helm chart getter for HTTP(s) repositories.\nfunc newHTTPGetter(url, certFile, keyFile, caFile string) (getter.Getter, error) {\n\treturn getter.NewHTTPGetter(url, certFile, keyFile, caFile)\n}\n\nfunc decodeManifests(manifests map[string]string) ([]*unstructured.Unstructured, error) {\n\tvar resources []*unstructured.Unstructured\n\tfor k, v := range manifests {\n\t\t// Sometimes README.md or NOTES.txt files make it into the template directory.\n\t\t// Filter files by extension.\n\t\tswitch filepath.Ext(k) {\n\t\tcase \".json\", \".yml\", \".yaml\":\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tresult := resource.NewLocalBuilder().\n\t\t\tContinueOnError().\n\t\t\tUnstructured().\n\t\t\tStream(bytes.NewBufferString(v), k).\n\t\t\tFlatten().\n\t\t\tDo()\n\t\tif result.Err() != nil {\n\t\t\treturn nil, fmt.Errorf(\"get manifest: %w\", result.Err())\n\t\t}\n\t\tinfos, err := result.Infos()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"get file information: %w\", err)\n\t\t}\n\t\tfor _, i := range infos {\n\t\t\tresources = append(resources, i.Object.(*unstructured.Unstructured))\n\t\t}\n\t}\n\treturn resources, nil\n}\n"
  },
  {
    "path": "src/go/pkg/controller/chartassignment/release_test.go",
    "content": "package chartassignment\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubetest\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/helm/pkg/chartutil\"\n)\n\nconst (\n\tChartName = \"testchart\"\n)\n\nfunc verifyValues(t *testing.T, have string, wantValues chartutil.Values) {\n\tif want, err := wantValues.YAML(); err != nil {\n\t\tt.Fatal(err)\n\t} else if want != have {\n\t\tt.Fatalf(\"config values do not match: want\\n%s\\n\\ngot\\n%s\\n\", want, have)\n\t}\n}\n\nfunc Test_loadChart_mergesValues(t *testing.T) {\n\tvar as apps.ChartAssignment\n\tunmarshalYAML(t, &as, `\nmetadata:\n  name: test-assignment-1\nspec:\n  chart:\n    values:\n      bar1: 4\n      bar2:\n        baz2: test\n\t`)\n\tas.Spec.Chart.Inline = kubetest.BuildInlineChart(t, ChartName /*template=*/, \"\", `\nfoo1:\n  baz1: \"hello\"\nbar1: 3`)\n\twantValues := chartutil.Values{\n\t\t\"bar1\": 4,\n\t\t\"bar2\": chartutil.Values{\"baz2\": \"test\"},\n\t\t\"foo1\": chartutil.Values{\"baz1\": \"hello\"},\n\t}\n\n\t_, vals, err := loadChart(&as.Spec.Chart)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tverifyValues(t, vals, wantValues)\n}\n\nfunc Test_loadChartWithoutTemplates_returnsZeroManifests(t *testing.T) {\n\tvar as apps.ChartAssignment\n\tunmarshalYAML(t, &as, `\nmetadata:\n  name: test-assignment-1\nspec:\n  chart:\n    values:\n\t`)\n\tas.Spec.Chart.Inline = kubetest.BuildInlineChart(t, ChartName /*template=*/, \"\", `foo: 1`)\n\tresources, _, err := loadAndExpandChart(&as)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(resources) > 0 {\n\t\tt.Errorf(\"Expected no resources, got %d\", len(resources))\n\t}\n\n}\n\nfunc Test_updateSynk_callsApply(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tvar as apps.ChartAssignment\n\tunmarshalYAML(t, &as, `\nmetadata:\n  name: test-assignment-1\nspec:\n  chart:\n    values:\n\t`)\n\tas.Spec.Chart.Inline = kubetest.BuildInlineChart(t, ChartName /*template=*/, \"\", `foo: 1`)\n\n\tmockSynk := NewMockInterface(ctrl)\n\tr := &release{\n\t\tsynk:     mockSynk,\n\t\trecorder: &record.FakeRecorder{},\n\t}\n\n\trs := &apps.ResourceSet{}\n\tmockSynk.EXPECT().Apply(gomock.Any(), \"test-assignment-1\", gomock.Any(), gomock.Any()).Return(rs, nil).Times(1)\n\n\t// First apply, the chart should be installed.\n\tr.update(&as)\n}\n\nfunc Test_deleteSynk_callsDelete(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tvar as apps.ChartAssignment\n\tunmarshalYAML(t, &as, `\nmetadata:\n  name: test-assignment-1\nspec:\n  chart:\n    values:\n\t`)\n\tas.Spec.Chart.Inline = kubetest.BuildInlineChart(t, ChartName /*template=*/, \"\", `foo: 1`)\n\n\tmockSynk := NewMockInterface(ctrl)\n\tr := &release{\n\t\tsynk:     mockSynk,\n\t\trecorder: &record.FakeRecorder{},\n\t}\n\n\tmockSynk.EXPECT().Delete(gomock.Any(), \"test-assignment-1\").Return(nil).Times(1)\n\n\t// First apply, the chart should be installed.\n\tr.delete(&as)\n}\n"
  },
  {
    "path": "src/go/pkg/controller/chartassignment/validator.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage chartassignment\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/validation\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// NewValidationWebhook returns a new webhook that validates ChartAssignments.\nfunc NewValidationWebhook(mgr manager.Manager) *admission.Webhook {\n\treturn &admission.Webhook{Handler: newChartAssignmentValidator(mgr.GetScheme())}\n}\n\n// chartAssignmentValidator implements a validation webhook.\ntype chartAssignmentValidator struct {\n\tdecoder runtime.Decoder\n}\n\nfunc newChartAssignmentValidator(sc *runtime.Scheme) *chartAssignmentValidator {\n\treturn &chartAssignmentValidator{\n\t\tdecoder: serializer.NewCodecFactory(sc).UniversalDeserializer(),\n\t}\n}\n\nfunc (v *chartAssignmentValidator) Handle(_ context.Context, req admission.Request) admission.Response {\n\tcur := &apps.ChartAssignment{}\n\told := &apps.ChartAssignment{}\n\n\tif err := runtime.DecodeInto(v.decoder, req.AdmissionRequest.Object.Raw, cur); err != nil {\n\t\treturn admission.Errored(http.StatusBadRequest, err)\n\t}\n\tif len(req.AdmissionRequest.OldObject.Raw) > 0 {\n\t\tif err := runtime.DecodeInto(v.decoder, req.AdmissionRequest.OldObject.Raw, old); err != nil {\n\t\t\treturn admission.Errored(http.StatusBadRequest, err)\n\t\t}\n\t} else {\n\t\told = nil\n\t}\n\tif err := v.validate(cur, old); err != nil {\n\t\treturn admission.Denied(err.Error())\n\t}\n\treturn admission.Allowed(\"\")\n}\n\nfunc (v *chartAssignmentValidator) validate(cur, old *apps.ChartAssignment) error {\n\tif cur.Spec.NamespaceName == \"\" {\n\t\treturn fmt.Errorf(\"namespace name missing\")\n\t}\n\terrs := validation.ValidateNamespaceName(cur.Spec.NamespaceName, false)\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"invalid namespace name %q: %s\", cur.Spec.NamespaceName, strings.Join(errs, \", \"))\n\t}\n\tif old != nil {\n\t\tif cur.Spec.NamespaceName != old.Spec.NamespaceName {\n\t\t\treturn fmt.Errorf(\"target namespace name must not be changed\")\n\t\t}\n\t}\n\tc := cur.Spec.Chart\n\tif c.Inline != \"\" {\n\t\tif c.Repository != \"\" || c.Name != \"\" {\n\t\t\treturn fmt.Errorf(\"chart repository, and name must be empty for inline charts\")\n\t\t}\n\t} else if c.Repository == \"\" || c.Name == \"\" || c.Version == \"\" {\n\t\treturn fmt.Errorf(\"non-inline chart must be fully specified\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/pkg/controller/chartassignment/validator_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage chartassignment\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc unmarshalYAML(t *testing.T, v interface{}, s string) {\n\tt.Helper()\n\tif err := yaml.Unmarshal([]byte(strings.TrimSpace(s)), v); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestValidate(t *testing.T) {\n\tcases := []struct {\n\t\tname       string\n\t\told        string\n\t\tcur        string\n\t\tshouldFail bool\n\t}{\n\t\t{\n\t\t\tname: \"valid-with-inline-chart\",\n\t\t\tcur: `\nspec:\n  namespaceName: ns1\n  chart:\n    inline: abc\n    values:\n      a: 2\n      b: {c: 3}\n\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"valid-with-reference-chart\",\n\t\t\tcur: `\nspec:\n  namespaceName: ns1\n  chart:\n    repository: https://some.repo\n    name: chartname\n    version: 1.3.4\n    values:\n      a: 2\n      b: {c: 3}\n\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"missing-namespace-name\",\n\t\t\tcur: `\nspec:\n  chart:\n    inline: abc\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid-namespace-name\",\n\t\t\tcur: `\nspec:\n  namespaceName: ns1%2\n  chart:\n    inline: abc\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid-partial-reference\",\n\t\t\tcur: `\nspec:\n  namespaceName: ns1%2\n  chart:\n    name: chartname\n    version: 1.3.4\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid-inline-and-reference-chart\",\n\t\t\tcur: `\nspec:\n  namespaceName: ns1%2\n  chart:\n    inline: abc\n    repository: https://some.repo\n    name: chartname\n    version: 1.3.4\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"namespace-name-changed\",\n\t\t\told: `\nspec:\n  namespaceName: ns1\n  chart:\n    inline: abc\n\t`,\n\t\t\tcur: `\nspec:\n  namespaceName: ns2\n  chart:\n    inline: abc\n\t`,\n\t\t\tshouldFail: true,\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tv := newChartAssignmentValidator(nil)\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tvar old, cur *apps.ChartAssignment\n\n\t\t\tif c.old != \"\" {\n\t\t\t\told = &apps.ChartAssignment{}\n\t\t\t\tunmarshalYAML(t, &old, c.old)\n\t\t\t}\n\t\t\tif c.cur != \"\" {\n\t\t\t\tcur = &apps.ChartAssignment{}\n\t\t\t\tunmarshalYAML(t, &cur, c.cur)\n\t\t\t}\n\t\t\terr := v.validate(cur, old)\n\t\t\tif err == nil && c.shouldFail {\n\t\t\t\tt.Fatal(\"expected failure but got none\")\n\t\t\t}\n\t\t\tif err != nil && !c.shouldFail {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/gcr/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"update_gcr_credentials.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/gcr\",\n    deps = [\n        \"//src/go/pkg/kubeutils:go_default_library\",\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\"update_gcr_credential_test.go\"],\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:private\"],\n    deps = [\"@com_github_onsi_gomega//:go_default_library\"],\n)\n"
  },
  {
    "path": "src/go/pkg/gcr/update_gcr_credential_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gcr\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestDockercfgJSON(t *testing.T) {\n\tg := NewGomegaWithT(t)\n\texpectedJSON := `{\n  \"https://gcr.io\":{\"username\":\"oauth2accesstoken\",\"password\":\"ya29.yaddayadda\",\"email\":\"not@val.id\",\"auth\":\"b2F1dGgyYWNjZXNzdG9rZW46eWEyOS55YWRkYXlhZGRh\"},\n  \"https://asia.gcr.io\":{\"username\":\"oauth2accesstoken\",\"password\":\"ya29.yaddayadda\",\"email\":\"not@val.id\",\"auth\":\"b2F1dGgyYWNjZXNzdG9rZW46eWEyOS55YWRkYXlhZGRh\"},\n  \"https://eu.gcr.io\":{\"username\":\"oauth2accesstoken\",\"password\":\"ya29.yaddayadda\",\"email\":\"not@val.id\",\"auth\":\"b2F1dGgyYWNjZXNzdG9rZW46eWEyOS55YWRkYXlhZGRh\"},\n  \"https://us.gcr.io\":{\"username\":\"oauth2accesstoken\",\"password\":\"ya29.yaddayadda\",\"email\":\"not@val.id\",\"auth\":\"b2F1dGgyYWNjZXNzdG9rZW46eWEyOS55YWRkYXlhZGRh\"}\n}`\n\n\tgotJSON := DockerCfgJSON(\"ya29.yaddayadda\")\n\n\tg.Expect(gotJSON).To(MatchJSON(expectedJSON))\n}\n"
  },
  {
    "path": "src/go/pkg/gcr/update_gcr_credentials.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/*\nLibrary for updating the token used to pull images from GCR in the surrounding cluster.\n*/\npackage gcr\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubeutils\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// Name of the secret that stores the GCR pull token.\nconst SecretName = \"gcr-json-key\"\n\n// DockerCfgJSON takes a service account key, and converts it into the JSON\n// format required for k8s's docker-registry secrets.\nfunc DockerCfgJSON(token string) []byte {\n\ttype dockercfg struct {\n\t\tUsername string `json:\"username\"`\n\t\tPassword string `json:\"password\"`\n\t\tEmail    string `json:\"email\"`\n\t\tAuth     []byte `json:\"auth\"`\n\t}\n\n\tm := map[string]interface{}{}\n\tfor _, r := range []string{\"gcr.io\", \"asia.gcr.io\", \"eu.gcr.io\", \"us.gcr.io\"} {\n\t\tm[\"https://\"+r] = dockercfg{\n\t\t\tUsername: \"oauth2accesstoken\",\n\t\t\tPassword: string(token),\n\t\t\tEmail:    \"not@val.id\",\n\t\t\tAuth:     []byte(\"oauth2accesstoken:\" + token),\n\t\t}\n\t}\n\tb, err := json.Marshal(m)\n\tif err != nil {\n\t\tslog.Error(\"unexpected error marshalling dockercfg\", ilog.Err(err))\n\t\tos.Exit(1)\n\t}\n\treturn b\n}\n\nfunc patchServiceAccount(ctx context.Context, k8s *kubernetes.Clientset, name string, namespace string, patchData []byte) error {\n\tsa := k8s.CoreV1().ServiceAccounts(namespace)\n\treturn backoff.Retry(\n\t\tfunc() error {\n\t\t\t_, err := sa.Patch(ctx, name, types.StrategicMergePatchType, patchData, metav1.PatchOptions{})\n\t\t\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\treturn backoff.Permanent(fmt.Errorf(\"failed to apply %q: %v\", patchData, err))\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t\t// Wait up to a minute for kube-controller-manager to create\n\t\t// the SA in new namespaces. I suspect there's a race condition\n\t\t// if the namespace is created and then quickly deleted\n\t\t// (b/281647304) which might cause this to time out - if we see\n\t\t// this error showing up a lot, we could check for namespace\n\t\t// deletion instead.\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t)\n}\n\n// UpdateGcrCredentials authenticates to the cloud cluster using the auth config given and updates\n// the credentials used to pull images from GCR.\nfunc UpdateGcrCredentials(ctx context.Context, k8s *kubernetes.Clientset, auth *robotauth.RobotAuth, gcpSaChain ...string) error {\n\ttokenSource := auth.CreateRobotTokenSource(ctx, gcpSaChain...)\n\ttoken, err := tokenSource.Token()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get token: %v\", err)\n\t}\n\n\t// First, update the default/gcr-json-key Secret, which is the\n\t// source-of-truth when creating new chart namespaces.\n\tcfgData := map[string][]byte{\".dockercfg\": DockerCfgJSON(token.AccessToken)}\n\tif err := kubeutils.UpdateSecret(ctx, k8s, &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      SecretName,\n\t\t\tNamespace: \"default\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t// The chart-assignment-controller looks for this label and copies\n\t\t\t\t// gcr-json-key to new namespaces, so that the pods can pull images.\n\t\t\t\t\"cloudrobotics.com/copy-to-chart-namespaces\": \"true\",\n\t\t\t},\n\t\t},\n\t\tType: corev1.SecretTypeDockercfg,\n\t\tData: cfgData,\n\t}); err != nil {\n\t\treturn fmt.Errorf(\"failed to update default/%s: %v\", SecretName, err)\n\t}\n\t// Tell k8s to use this key by pointing the default SA at it.\n\tpatchData := []byte(`{\"imagePullSecrets\": [{\"name\": \"` + SecretName + `\"}]}`)\n\tif err := patchServiceAccount(ctx, k8s, \"default\", \"default\", patchData); err != nil {\n\t\treturn fmt.Errorf(\"failed to update kubernetes service account for namespace default: %v\", err)\n\t}\n\n\tnsList, err := k8s.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list namespaces: %v\", err)\n\t}\n\thaveError := false\n\tfor _, ns := range nsList.Items {\n\t\tif ns.DeletionTimestamp != nil {\n\t\t\tslog.Info(\"namespace is marked for deletion, skipping\", slog.String(\"Namespace\", ns.ObjectMeta.Name))\n\t\t\tcontinue\n\t\t}\n\t\tnamespace := ns.ObjectMeta.Name\n\t\tif namespace == \"default\" {\n\t\t\t// Handled above.\n\t\t\tcontinue\n\t\t}\n\n\t\t// Only ever create secrets in a few specific, well-known namespaces. For app-* namespaces\n\t\t// the ChartAssignment controller will create the initial secret and patch the service account.\n\t\t// This avoids us putting pull secrets into eg foreign namespaces.\n\t\ts := k8s.CoreV1().Secrets(namespace)\n\t\tif _, err := s.Get(ctx, SecretName, metav1.GetOptions{}); k8serrors.IsNotFound(err) {\n\t\t\tif namespace != \"kube-system\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\t// If we get here, the namespace has a secret that we need to update or\n\t\t// it is the kube-system namespace where it is okay to create the secret.\n\n\t\t// Create or update a secret containing a docker config with the access-token.\n\t\terr = kubeutils.UpdateSecret(ctx, k8s, &corev1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      SecretName,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t\tType: corev1.SecretTypeDockercfg,\n\t\t\tData: cfgData,\n\t\t})\n\t\tif err != nil {\n\t\t\tslog.Error(\"failed to update kubernetes secret\",\n\t\t\t\tslog.String(\"Namespace\", namespace),\n\t\t\t\tilog.Err(err))\n\t\t\thaveError = true\n\t\t\tcontinue\n\t\t}\n\t\t// Tell k8s to use this key by pointing the default SA at it.\n\t\terr = patchServiceAccount(ctx, k8s, \"default\", namespace, patchData)\n\t\tif err != nil {\n\t\t\tslog.Error(\"failed to update kubernetes service account\",\n\t\t\t\tslog.String(\"Namespace\", namespace),\n\t\t\t\tilog.Err(err))\n\t\t\thaveError = true\n\t\t}\n\t}\n\tif haveError {\n\t\treturn fmt.Errorf(\"failed to update one or more namespaces\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/go/pkg/kubetest/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"kubetest.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/kubetest\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/gcr:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//apps/v1:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_api//rbac/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/scheme:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//tools/clientcmd:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library\",\n        \"@io_k8s_sigs_kind//pkg/apis/config/defaults:go_default_library\",\n        \"@io_k8s_sigs_kind//pkg/apis/config/v1alpha4:go_default_library\",\n        \"@io_k8s_sigs_kind//pkg/cluster:go_default_library\",\n        \"@io_k8s_sigs_yaml//:go_default_library\",\n        \"@org_golang_x_sync//errgroup:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/kubetest/kubetest.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package kubetest provides functionality to create local Kubernetes test\n// clusters and run tests against them.\npackage kubetest\n\nimport (\n\t\"archive/tar\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\tcrcapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/gcr\"\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/sync/errgroup\"\n\tapps \"k8s.io/api/apps/v1\"\n\tcore \"k8s.io/api/core/v1\"\n\trbac \"k8s.io/api/rbac/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tk8sruntime \"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tkinddefaults \"sigs.k8s.io/kind/pkg/apis/config/defaults\"\n\tkindconfig \"sigs.k8s.io/kind/pkg/apis/config/v1alpha4\"\n\tkindcluster \"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// Environment encapsulates a set of clusters and can run tests against them.\ntype Environment struct {\n\tt           *testing.T\n\tcfg         Config\n\tscheme      *k8sruntime.Scheme\n\thelmPath    string\n\tsynkPath    string\n\tclusters    map[string]*cluster\n\tuniqCounter int\n}\n\ntype Config struct {\n\t// The clusters that are provisioned for the test environment.\n\tClusters []ClusterConfig\n\n\t// Registration function for additional resource types to a scheme.\n\tSchemeFunc func(*k8sruntime.Scheme) error\n}\n\ntype ClusterConfig struct {\n\tName string\n}\n\ntype cluster struct {\n\tgenName        string\n\tcfg            ClusterConfig\n\tkind           *kindcluster.Provider\n\tkubeConfigPath string\n\trestCfg        *rest.Config\n}\n\n// New creates a new test environment.\nfunc New(t *testing.T, cfg Config) *Environment {\n\te := &Environment{\n\t\thelmPath: \"../+non_module_deps+kubernetes_helm/helm\",\n\t\tsynkPath: \"src/go/cmd/synk/synk_/synk\",\n\t\tt:        t,\n\t\tcfg:      cfg,\n\t\tscheme:   k8sruntime.NewScheme(),\n\t\tclusters: map[string]*cluster{},\n\t}\n\tif cfg.SchemeFunc != nil {\n\t\tcfg.SchemeFunc(e.scheme)\n\t}\n\tscheme.AddToScheme(e.scheme)\n\n\tvar g errgroup.Group\n\t// Setup cluster concurrently.\n\tfor _, cfg := range cfg.Clusters {\n\t\t// Make name unique to avoid collisions across parallel tests.\n\t\tuniqName := fmt.Sprintf(\"%s-%x\", cfg.Name, time.Now().UnixNano())\n\t\tt.Logf(\"Assigned unique name %q to cluster %q\", uniqName, cfg.Name)\n\n\t\tcluster := &cluster{\n\t\t\tgenName: uniqName,\n\t\t\tcfg:     cfg,\n\t\t}\n\t\te.clusters[cfg.Name] = cluster\n\n\t\tg.Go(func() error {\n\t\t\tif err := setupCluster(e.synkPath, cluster); err != nil {\n\t\t\t\t// If cluster has already been created, delete it.\n\t\t\t\tif cluster.kind != nil && os.Getenv(\"NO_TEARDOWN\") == \"\" {\n\t\t\t\t\tcluster.kind.Delete(cfg.Name, \"\")\n\t\t\t\t\tif cluster.kubeConfigPath != \"\" {\n\t\t\t\t\t\tos.Remove(cluster.kubeConfigPath)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn errors.Wrapf(err, \"Create cluster %q\", cfg.Name)\n\t\t\t}\n\t\t\tslog.Info(\"Created cluster\", slog.String(\"Name\", cfg.Name))\n\t\t\treturn nil\n\t\t})\n\t}\n\tif err := g.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn e\n}\n\nfunc (e *Environment) Ctx() context.Context {\n\treturn context.Background()\n}\n\nfunc helmValues(vars map[string]string) string {\n\tlist := []string{}\n\tfor k, v := range vars {\n\t\tlist = append(list, fmt.Sprintf(\"%s=%s\", k, v))\n\t}\n\treturn strings.Join(list, \",\")\n}\n\n// InstallChartArchive installs a Helm chart from a tarball on disk into a cluster.\n// Arguments are provided as a map where the keys are JSON paths.\nfunc (e *Environment) InstallChartArchive(cluster, name, namespace, path string, args map[string]string) {\n\tc, ok := e.clusters[cluster]\n\tif !ok {\n\t\te.t.Fatalf(\"Unknown cluster %q\", cluster)\n\t}\n\n\toutput, err := exec.Command(\n\t\te.helmPath,\n\t\t\"template\",\n\t\t\"--set-string\", helmValues(args),\n\t\t\"--name\", name,\n\t\tpath,\n\t).CombinedOutput()\n\tif err != nil {\n\t\te.t.Fatalf(\"Synk install of %s failed: %v\\nHelm output:\\n%s\\n\", name, err, output)\n\t}\n\tcmd := exec.Command(\n\t\te.synkPath,\n\t\t\"apply\",\n\t\tname,\n\t\t\"--kubeconfig\", c.kubeConfigPath,\n\t\t\"-n\", namespace,\n\t\t\"-f\", \"-\",\n\t)\n\t// Helm writes the templated manifests and errors alike to stderr.\n\t// So we can just take the combined output as is.\n\tcmd.Stdin = bytes.NewReader(output)\n\n\tif output, err = cmd.CombinedOutput(); err != nil {\n\t\te.t.Fatalf(\"Synk install of %s failed: %v\\nSynk output:\\n%s\\n\", name, err, output)\n\t}\n}\n\n// Client returns a new client for the cluster.\nfunc (e *Environment) Client(cluster string) client.Client {\n\tc, ok := e.clusters[cluster]\n\tif !ok {\n\t\te.t.Fatalf(\"cluster with name %q does not exist\", cluster)\n\t}\n\tclient, err := client.New(c.restCfg, client.Options{\n\t\tScheme: e.scheme,\n\t})\n\tif err != nil {\n\t\te.t.Fatalf(\"Create client for cluster %q: %s\", cluster, err)\n\t}\n\treturn client\n}\n\n// Teardown destroys all clusters that were created for the environment.\nfunc (e *Environment) Teardown() {\n\tif os.Getenv(\"NO_TEARDOWN\") != \"\" {\n\t\tslog.Info(\"Skipping teardown\")\n\t\treturn\n\t}\n\tslog.Info(\"Tearing down...\")\n\n\tfor name, c := range e.clusters {\n\t\tif err := c.kind.Delete(c.genName, \"\"); err != nil {\n\t\t\te.t.Errorf(\"Delete cluster %q (%q): %s\", name, c.genName, err)\n\t\t} else {\n\t\t\tslog.Info(\"Deleted cluster\",\n\t\t\t\tslog.String(\"Name\", name),\n\t\t\t\tslog.String(\"GenName\", c.genName))\n\t\t}\n\t\tif err := os.Remove(c.kubeConfigPath); err != nil {\n\t\t\te.t.Errorf(\"Failed to delete %q: %s\", c.kubeConfigPath, err)\n\t\t}\n\t}\n}\n\ntype TestFunc func(*testing.T, *Fixture)\n\n// Run takes a list of TestFuncs and executes them as subtests.\nfunc (e *Environment) Run(tests ...TestFunc) {\n\tfor _, test := range tests {\n\t\tf := e.New(test)\n\t\te.t.Run(f.name, func(t *testing.T) {\n\t\t\t// Recover from panics to ensure that defer Teardown will run.\n\t\t\tdefer func() {\n\t\t\t\tif err := recover(); err != nil {\n\t\t\t\t\tt.Errorf(\"panic: %s\", err)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tf.t = t\n\t\t\tf.testFn(t, f)\n\t\t})\n\t}\n}\n\n// Uniq takes a string and makes it unique. It should be used to generate collision-free\n// names for namespaces or cluster-wide resources from subtests.\nfunc (e *Environment) Uniq(s string) string {\n\te.uniqCounter++\n\treturn fmt.Sprintf(\"%s-%d\", s, e.uniqCounter)\n}\n\n// Fixture provides functionality for a single test that is run against an environment.\ntype Fixture struct {\n\tt      *testing.T\n\tname   string\n\tenv    *Environment\n\ttestFn TestFunc\n}\n\n// New creates a new Fixture for a test function.\nfunc (env *Environment) New(testFn TestFunc) *Fixture {\n\treturn &Fixture{\n\t\tname:   runtime.FuncForPC(reflect.ValueOf(testFn).Pointer()).Name(),\n\t\ttestFn: testFn,\n\t\tenv:    env,\n\t}\n}\n\nfunc (f *Fixture) Ctx() context.Context {\n\treturn context.Background()\n}\n\n// ObjectKey extracts a namespace/name key from the given object.\nfunc (f *Fixture) ObjectKey(o client.Object) client.ObjectKey {\n\tk := client.ObjectKeyFromObject(o)\n\treturn k\n}\n\n// Uniq takes a string and makes it unique. It should be used to generate collision-free\n// names for namespaces or cluster-wide resources from subtests.\nfunc (f *Fixture) Uniq(s string) string {\n\treturn f.env.Uniq(s)\n}\n\n// FromYAML expands a YAML template with the given vals and unmarshals it into dst.\n// dst is typically of type *unstructured.Unstructured or a fully specified type\n// for a Kubernetes resource.\nfunc (f *Fixture) FromYAML(tmpl string, vals, dst interface{}) {\n\tf.t.Helper()\n\n\tt, err := template.New(\"\").Parse(tmpl)\n\tif err != nil {\n\t\tf.t.Fatalf(\"Invalid template: %s\", err)\n\t}\n\tvar buf bytes.Buffer\n\tif err := t.Execute(&buf, vals); err != nil {\n\t\tf.t.Fatalf(\"Execute template: %s\", err)\n\t}\n\tif err := yaml.Unmarshal(bytes.TrimSpace(buf.Bytes()), dst); err != nil {\n\t\tf.t.Fatal(err)\n\t}\n}\n\n// BuildInlineChart creates an inline chart string with the given name,\n// template and values.\nfunc BuildInlineChart(t *testing.T, name, tmpl, values string) string {\n\tt.Helper()\n\n\tchartData := fmt.Sprintf(`{name: %q, version: \"0.0.1\"}`, name)\n\n\tvar encoded bytes.Buffer\n\tbw := base64.NewEncoder(base64.StdEncoding, &encoded)\n\tzw := gzip.NewWriter(bw)\n\ttw := tar.NewWriter(zw)\n\tif err := addFileToTar(tw, name+\"/Chart.yaml\", chartData); err != nil {\n\t\tt.Fatalf(\"Failed to add Chart.yaml to tarball: %s\", err)\n\t}\n\taddFileToTar(tw, name+\"/templates/template.yaml\", tmpl)\n\taddFileToTar(tw, name+\"/values.yaml\", values)\n\ttw.Close()\n\tzw.Close()\n\tbw.Close()\n\treturn encoded.String()\n}\n\nfunc addFileToTar(tw *tar.Writer, path, content string) error {\n\tif err := tw.WriteHeader(&tar.Header{\n\t\tName: path,\n\t\tSize: int64(len(content)),\n\t}); err != nil {\n\t\treturn err\n\t}\n\tif _, err := io.WriteString(tw, content); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Client returns a new client for the cluster.\nfunc (f *Fixture) Client(cluster string) client.Client {\n\tf.t.Helper()\n\treturn f.env.Client(cluster)\n}\n\n// setupCluster creates a kind cluster and installs synk if necessary.\nfunc setupCluster(synkPath string, cluster *cluster) error {\n\tkindcfg := &kindconfig.Cluster{\n\t\tNodes: []kindconfig.Node{\n\t\t\t{\n\t\t\t\tRole:  kindconfig.ControlPlaneRole,\n\t\t\t\tImage: kinddefaults.Image,\n\t\t\t}, {\n\t\t\t\tRole:  kindconfig.WorkerRole,\n\t\t\t\tImage: kinddefaults.Image,\n\t\t\t},\n\t\t},\n\t}\n\tcluster.kind = kindcluster.NewProvider()\n\n\t// Create kubeconfig file for use by synk or the dev.\n\tkubeConfig, err := os.CreateTemp(\"\", \"kubeconfig-\")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"create temp kubeconfig\")\n\t}\n\tcluster.kubeConfigPath = kubeConfig.Name()\n\tif err := kubeConfig.Close(); err != nil {\n\t\treturn errors.Wrap(err, \"close temp kubeconfig\")\n\t}\n\n\tif err := cluster.kind.Create(\n\t\tcluster.genName,\n\t\tkindcluster.CreateWithV1Alpha4Config(kindcfg),\n\t\tkindcluster.CreateWithKubeconfigPath(cluster.kubeConfigPath),\n\t); err != nil {\n\t\treturn errors.Wrapf(err, \"create cluster %q\", cluster.genName)\n\t}\n\tkubecfgRaw, err := os.ReadFile(cluster.kubeConfigPath)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"read kube config\")\n\t}\n\tkubecfg, err := clientcmd.NewClientConfigFromBytes(kubecfgRaw)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"decode kube config\")\n\t}\n\tcluster.restCfg, err = kubecfg.ClientConfig()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"get rest config\")\n\t}\n\tfmt.Printf(\"To use the cluster, run KUBECONFIG=%s kubectl cluster-info\", cluster.kubeConfigPath)\n\n\t// Setup permissive binding we also have in cloud and robot clusters.\n\tctx := context.Background()\n\n\tc, err := client.New(cluster.restCfg, client.Options{})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"create client\")\n\t}\n\tif err := c.Create(ctx, &rbac.ClusterRoleBinding{\n\t\tObjectMeta: meta.ObjectMeta{\n\t\t\tName: \"permissive-binding\",\n\t\t},\n\t\tRoleRef: rbac.RoleRef{\n\t\t\tAPIGroup: \"rbac.authorization.k8s.io\",\n\t\t\tKind:     \"ClusterRole\",\n\t\t\tName:     \"cluster-admin\",\n\t\t},\n\t\tSubjects: []rbac.Subject{{\n\t\t\tAPIGroup: \"rbac.authorization.k8s.io\",\n\t\t\tKind:     \"Group\",\n\t\t\tName:     \"system:serviceaccounts\",\n\t\t}},\n\t}); err != nil {\n\t\treturn errors.Wrap(err, \"create permissive role binding\")\n\t}\n\n\t// Setup service account and create image pull secrets.\n\tif token := os.Getenv(\"ACCESS_TOKEN\"); token != \"\" {\n\t\t// Use the same secret name as the GCR credential refresher would\n\t\t// on robots.\n\t\t// This makes some testing of components easier, that assume this\n\t\t// secret to exist, e.g. ChartAssignment controller.\n\t\tsecret := &core.Secret{\n\t\t\tObjectMeta: meta.ObjectMeta{\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tName:      gcr.SecretName,\n\t\t\t},\n\t\t\tType: core.SecretTypeDockercfg,\n\t\t\tData: map[string][]byte{\n\t\t\t\t\".dockercfg\": gcr.DockerCfgJSON(token),\n\t\t\t},\n\t\t}\n\t\tif err := c.Create(ctx, secret); err != nil {\n\t\t\treturn errors.Wrap(err, \"create pull secret\")\n\t\t}\n\t\tif err := backoff.Retry(\n\t\t\tfunc() error {\n\t\t\t\tvar sa core.ServiceAccount\n\t\t\t\terr := c.Get(ctx, client.ObjectKey{\"default\", \"default\"}, &sa)\n\t\t\t\tif k8serrors.IsNotFound(err) {\n\t\t\t\t\treturn errors.New(\"not found\")\n\t\t\t\t} else if err != nil {\n\t\t\t\t\treturn backoff.Permanent(errors.Wrap(err, \"get service account\"))\n\t\t\t\t}\n\t\t\t\tsa.ImagePullSecrets = append(sa.ImagePullSecrets, core.LocalObjectReference{\n\t\t\t\t\tName: gcr.SecretName,\n\t\t\t\t})\n\t\t\t\tif err = c.Update(ctx, &sa); k8serrors.IsConflict(err) {\n\t\t\t\t\treturn fmt.Errorf(\"conflict\")\n\t\t\t\t} else if err != nil {\n\t\t\t\t\treturn backoff.Permanent(errors.Wrap(err, \"update service account\"))\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t\t); err != nil {\n\t\t\treturn errors.Wrap(err, \"inject pull secret\")\n\t\t}\n\t}\n\n\t// Wait for a node to be ready, by checking for node taints (incl. NotReady)\n\t// (context: b/128660997)\n\tif err := backoff.Retry(\n\t\tfunc() error {\n\t\t\tvar nds core.NodeList\n\t\t\tif err := c.List(ctx, &nds); err != nil {\n\t\t\t\treturn backoff.Permanent(err)\n\t\t\t}\n\t\t\tfor _, n := range nds.Items {\n\t\t\t\tif len(n.Spec.Taints) == 0 {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"taints not removed\")\n\t\t},\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 240),\n\t); err != nil {\n\t\treturn errors.Wrap(err, \"wait for node taints to be removed\")\n\t}\n\tcmd := exec.Command(\n\t\tsynkPath,\n\t\t\"init\",\n\t\t\"--kubeconfig\", cluster.kubeConfigPath,\n\t)\n\tif output, err := cmd.CombinedOutput(); err != nil {\n\t\treturn errors.Errorf(\"install Helm: %v; output:\\n%s\\n\", err, output)\n\t}\n\treturn nil\n}\n\n// DeploymentReady returns a condition func that checks whether all replicas of a deployment\n// are available.\nfunc DeploymentReady(ctx context.Context, c client.Client, namespace, name string) error {\n\tvar d apps.Deployment\n\tif err := c.Get(ctx, client.ObjectKey{namespace, name}, &d); err != nil {\n\t\treturn backoff.Permanent(errors.Wrapf(err, \"get deployment %s/%s\", namespace, name))\n\t}\n\tif d.Spec.Replicas == nil {\n\t\tif d.Status.ReadyReplicas <= 0 {\n\t\t\treturn fmt.Errorf(\"Replicas not ready\")\n\t\t}\n\t\treturn nil\n\t} else if d.Status.ReadyReplicas != *d.Spec.Replicas {\n\t\treturn fmt.Errorf(\"Replicas not ready\")\n\t}\n\treturn nil\n}\n\n// ChartAssignmentHasStatus returns a condition func that checks if a given\n// ChartAssignment has the expected status. Calls to the condition func update\n// the ChartAssignment in place.\nfunc (f *Fixture) ChartAssignmentHasStatus(ca *crcapps.ChartAssignment, expected crcapps.ChartAssignmentPhase) func() error {\n\tclient := f.Client(ca.Spec.ClusterName)\n\treturn func() error {\n\t\tif err := client.Get(f.Ctx(), f.ObjectKey(ca), ca); err != nil {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\tif expected == crcapps.ChartAssignmentPhaseSettled && ca.Status.Phase == crcapps.ChartAssignmentPhaseReady {\n\t\t\t// phase can go straight from Updating->Ready and skip Settled, this is OK.\n\t\t\treturn nil\n\t\t}\n\t\tif ca.Status.Phase != expected {\n\t\t\tf.t.Logf(\"Status: %+v\", ca.Status)\n\t\t\treturn fmt.Errorf(\"chart status != %s\", expected)\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/kubeutils/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"kubeutils.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/kubeutils\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//tools/clientcmd:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/kubeutils/kubeutils.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage kubeutils\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/cenkalti/backoff\"\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/oauth2\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nconst (\n\tLocalContext = \"kubernetes-admin@kubernetes\"\n\n\tlocalConfig            = \"~/.kube/config\"\n\tdeletionTimeoutSeconds = 60\n)\n\n// Expand paths of the form \"~/path\" to absolute paths.\nfunc ExpandUser(path string) string {\n\tif path[:2] != \"~/\" {\n\t\treturn path\n\t}\n\tusr, _ := user.Current()\n\treturn filepath.Join(usr.HomeDir, path[2:])\n}\n\n// CloudKubernetesContextName generates the name of the cloud kubernetes context from the GCP\n// project ID and region.\nfunc CloudKubernetesContextName(projectID, region string) string {\n\treturn fmt.Sprintf(\"gke_%s_%s-c_cloud-robotics\", projectID, region)\n}\n\n// GetCloudKubernetesContext returns the name of the cloud kubernetes context.\nfunc GetCloudKubernetesContext() (string, error) {\n\tgcpProjectID, defined := os.LookupEnv(\"GCP_PROJECT_ID\")\n\tif !defined {\n\t\treturn \"\", fmt.Errorf(\"GCP_PROJECT_ID environment variable is not defined\")\n\t}\n\tgcpRegion, defined := os.LookupEnv(\"GCP_REGION\")\n\tif !defined {\n\t\treturn \"\", fmt.Errorf(\"GCP_REGION environment variable is not defined\")\n\t}\n\n\treturn CloudKubernetesContextName(gcpProjectID, gcpRegion), nil\n}\n\n// GetRobotKubernetesContext returns the name of the robot kubernetes context provided by the\n// kubernetes-relay-client.\nfunc GetRobotKubernetesContext() (string, error) {\n\tgcpProjectID, defined := os.LookupEnv(\"GCP_PROJECT_ID\")\n\tif !defined {\n\t\treturn \"\", fmt.Errorf(\"GCP_PROJECT_ID environment variable is not defined\")\n\t}\n\n\treturn fmt.Sprintf(\"%s-robot\", gcpProjectID), nil\n}\n\n// LoadOutOfClusterConfig loads a local kubernetes config on the robot or workstation.\nfunc LoadOutOfClusterConfigLocal() (*rest.Config, error) {\n\treturn LoadOutOfClusterConfig(LocalContext)\n}\n\nfunc LoadOutOfClusterConfig(context string) (*rest.Config, error) {\n\tloadingRules := clientcmd.NewDefaultClientConfigLoadingRules()\n\tloadingRules.ExplicitPath = ExpandUser(localConfig)\n\toverrides := &clientcmd.ConfigOverrides{}\n\toverrides.CurrentContext = context\n\tcfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)\n\treturn cfg.ClientConfig()\n}\n\n// PrefixingRoundtripper is a HTTP roundtripper that adds a specified prefix to\n// all HTTP requests. We need to use it instead of setting APIPath because\n// autogenerated and dynamic Kubernetes clients overwrite the REST config's\n// APIPath.\ntype PrefixingRoundtripper struct {\n\tPrefix string\n\tBase   http.RoundTripper\n}\n\nfunc (pr *PrefixingRoundtripper) RoundTrip(r *http.Request) (*http.Response, error) {\n\t// Avoid an extra roundtrip for the protocol upgrade\n\tr.URL.Scheme = \"https\"\n\tif !strings.HasPrefix(r.URL.Path, pr.Prefix+\"/\") {\n\t\tr.URL.Path = pr.Prefix + r.URL.Path\n\t}\n\tresp, err := pr.Base.RoundTrip(r)\n\treturn resp, err\n}\n\n// BuildCloudKubernetesConfig build a kubernetes config for authenticated access to the cloud\n// project.\nfunc BuildCloudKubernetesConfig(ts oauth2.TokenSource, remoteServer string) *rest.Config {\n\treturn &rest.Config{\n\t\tHost:    remoteServer,\n\t\tAPIPath: \"/apis\",\n\t\tWrapTransport: func(base http.RoundTripper) http.RoundTripper {\n\t\t\trt := &PrefixingRoundtripper{\n\t\t\t\tPrefix: \"/apis/core.kubernetes\",\n\t\t\t\tBase:   &oauth2.Transport{Source: ts, Base: base},\n\t\t\t}\n\t\t\treturn rt\n\t\t},\n\t}\n}\n\n// UpdateSecret (over-) writes a k8s secret.\nfunc UpdateSecret(ctx context.Context, k8s kubernetes.Interface, input *corev1.Secret) error {\n\ts := k8s.CoreV1().Secrets(input.Namespace)\n\tb := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5)\n\treturn backoff.Retry(func() error {\n\t\tsecret, err := s.Get(ctx, input.Name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tif k8serrors.IsNotFound(err) {\n\t\t\t\t_, err = s.Create(ctx, input, metav1.CreateOptions{})\n\t\t\t\treturn backoff.Permanent(errors.Wrap(err, \"create secret\"))\n\t\t\t}\n\t\t\treturn backoff.Permanent(errors.Wrap(err, \"get secret\"))\n\t\t}\n\t\tsecret.Labels = input.Labels\n\t\tsecret.Annotations = input.Annotations\n\t\tsecret.Data = input.Data\n\t\t_, err = s.Update(ctx, secret, metav1.UpdateOptions{})\n\t\tif k8serrors.IsConflict(err) {\n\t\t\t// Retry conflicts.\n\t\t\treturn err\n\t\t}\n\t\treturn backoff.Permanent(errors.Wrap(err, \"update secret\"))\n\t}, b)\n\n}\n"
  },
  {
    "path": "src/go/pkg/robotauth/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"robotauth.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/kubeutils:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n        \"@org_golang_x_oauth2//jws:go_default_library\",\n        \"@org_golang_x_oauth2//jwt:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"robotauth_test.go\"],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"@io_k8s_client_go//kubernetes/fake:go_default_library\",\n        \"@org_golang_x_oauth2//jws:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/robotauth/robotauth.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// The robotauth package contains the class for reading and writing the\n// robot-id.json file. This file contains the id & private key of a robot\n// that's connected to a Cloud project.\npackage robotauth\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/mail\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubeutils\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/jws\"\n\t\"golang.org/x/oauth2/jwt\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nconst (\n\t// TODO(ensonic): setup-dev creates a key and stores it, only for the ssh-app to read it\n\tcredentialsFile = \"~/.config/cloud-robotics/robot-id.json\"\n)\n\n// Object containing ID, as stored in robot-id.json.\ntype RobotAuth struct {\n\tRobotName           string `json:\"id\"`\n\tProjectId           string `json:\"project_id\"`\n\tPublicKeyRegistryId string `json:\"public_key_registry_id\"`\n\tPrivateKey          []byte `json:\"private_key\"`\n\tDomain              string `json:\"domain\"`\n}\n\nfunc filename() string {\n\treturn kubeutils.ExpandUser(credentialsFile)\n}\n\n// LoadFromFile loads key from json file. If keyfile is \"\", it tries to load\n// from the default location.\nfunc LoadFromFile(keyfile string) (*RobotAuth, error) {\n\tif keyfile == \"\" {\n\t\tkeyfile = filename()\n\t}\n\traw, err := os.ReadFile(keyfile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read %v: %v\", credentialsFile, err)\n\t}\n\n\tvar robotAuth RobotAuth\n\terr = json.Unmarshal(raw, &robotAuth)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse %v: %v\", credentialsFile, err)\n\t}\n\n\treturn &robotAuth, nil\n}\n\nfunc LoadFromK8sSecret(ctx context.Context, clientset kubernetes.Interface, namespace string) (*RobotAuth, error) {\n\ts, err := clientset.CoreV1().Secrets(namespace).Get(ctx, \"robot-auth\", metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tencoded, ok := s.Data[\"json\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"could not find json key in secret's data\")\n\t}\n\tvar ret RobotAuth\n\tif err := json.NewDecoder(bytes.NewReader(encoded)).Decode(&ret); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &ret, nil\n}\n\n// StoreInFile writes a newly-chosen ID to disk.\nfunc (r *RobotAuth) StoreInFile() error {\n\traw, err := json.Marshal(r)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to serialize ID: %v\", err)\n\t}\n\n\tfile := filename()\n\tif err := os.MkdirAll(kubeutils.ExpandUser(filepath.Dir(file)), 0700); err != nil {\n\t\treturn err\n\t}\n\n\terr = os.WriteFile(file, raw, 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write %v: %v\", credentialsFile, err)\n\t}\n\n\treturn nil\n}\n\n// StoreInK8sSecret writes new robot-id to kubernetes secret.\nfunc (r *RobotAuth) StoreInK8sSecret(ctx context.Context, clientset kubernetes.Interface, namespace string) error {\n\traw, err := json.Marshal(r)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to serialize ID: %v\", err)\n\t}\n\n\treturn kubeutils.UpdateSecret(ctx, clientset, &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"robot-auth\",\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tType: corev1.SecretTypeOpaque,\n\t\tData: map[string][]byte{\n\t\t\t\"json\": raw,\n\t\t},\n\t})\n}\n\n// CreatePrivateKey creates a private key.\n// The private key is written to the RobotAuth struct.\nfunc (r *RobotAuth) CreatePrivateKey() error {\n\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpkcs8, err := x509.MarshalPKCS8PrivateKey(key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.PrivateKey = pem.EncodeToMemory(&pem.Block{\n\t\tType:  \"PRIVATE KEY\",\n\t\tBytes: pkcs8,\n\t})\n\treturn nil\n}\n\nfunc (r *RobotAuth) getTokenEndpoint() string {\n\treturn fmt.Sprintf(\"https://%s/apis/core.token-vendor/v1/token.oauth2\", r.Domain)\n}\n\n// CreateRobotTokenSource creates an OAuth2 token source for the token vendor.\n// This token source returns Google Cloud access token minted for either\n// the robot-service@ service account or impersonated via provided gcpSaChain.\nfunc (r *RobotAuth) CreateRobotTokenSource(ctx context.Context, gcpSaChain ...string) oauth2.TokenSource {\n\tc := jwt.Config{\n\t\t// Will be used as \"issuer\" of the outgoing JWT. Is not formatted as an email though\n\t\tEmail:      r.PublicKeyRegistryId,\n\t\tExpires:    time.Minute * 30,\n\t\tPrivateKey: r.PrivateKey,\n\t\tScopes:     []string{},\n\t\tTokenURL:   r.getTokenEndpoint(),\n\t}\n\tif len(gcpSaChain) > 0 {\n\t\t// TokenVendor expects single service-account email.\n\t\t// todo: consider allowing token-vendor to accept SA chain.\n\t\tc.Subject = gcpSaChain[0]\n\t}\n\treturn c.TokenSource(ctx)\n}\n\n// CreateJWT allows to create a JWT for authentication against the token vendor.\n// This does not grant Google Cloud access, but can be used for explicit\n// authentication with the token vendor.\nfunc (r *RobotAuth) CreateJWT(ctx context.Context, lifetime time.Duration) (string, error) {\n\tp, _ := pem.Decode(r.PrivateKey)\n\tif p == nil {\n\t\treturn \"\", fmt.Errorf(\"decode private key\")\n\t}\n\tparsedKey, err := x509.ParsePKCS8PrivateKey(p.Bytes)\n\tif err != nil {\n\t\tparsedKey, err = x509.ParsePKCS1PrivateKey(p.Bytes)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v\", err)\n\t\t}\n\t}\n\tparsed, ok := parsedKey.(*rsa.PrivateKey)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"private key is invalid\")\n\t}\n\n\t// We re-use the token audience here.\n\t// While it would be nicer to use a specific token.verify endpoint here,\n\t// the token-vendor takes a full path it verifies.\n\t// This would allow this token to be used for getting an OAuth token, but\n\t// the token and identity endpoints use the same access protection,\n\t// so there's no functional difference either way.\n\tclaimSet := &jws.ClaimSet{\n\t\tIss: r.PublicKeyRegistryId,\n\t\tAud: r.getTokenEndpoint(),\n\t\tSub: r.RobotName,\n\t\tPrn: r.RobotName,\n\t\tExp: time.Now().Add(lifetime).Unix(),\n\t}\n\n\tret, err := jws.Encode(&jws.Header{Algorithm: \"RS256\", Typ: \"JWT\"}, claimSet, parsed)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn ret, nil\n}\n\n// ServiceAccountEmail takes name of service account and returns its\n// email form for given RobotAuth.\n//\n// Note: method will also accept SA in email form and return it AS-IS if it\n// resembles GCP service account. This allows caller to use this method\n// transparently for a situation where SA from different project than\n// RobotAuth#ProjectId is needed for robot. This should be very rare.\nfunc (r *RobotAuth) ServiceAccountEmail(saName string) (string, error) {\n\tif saName == \"\" {\n\t\treturn \"\", errors.New(\"empty name\")\n\t}\n\n\tif _, err := mail.ParseAddress(saName); err != nil {\n\t\treturn fmt.Sprintf(\"%s@%s.iam.gserviceaccount.com\", saName, r.ProjectId), nil\n\t}\n\n\tif !strings.HasSuffix(saName, \".iam.gserviceaccount.com\") {\n\t\treturn \"\", fmt.Errorf(\"unexpected service account email value, %s\", saName)\n\t}\n\n\treturn saName, nil\n}\n\n// robotJWTSource gets robot JWTs from the metadata-server.\ntype robotJWTSource struct {\n\tclient http.Client\n}\n\n// Token gets a robot JWT from the metadata-server or returns an error.\n// Token must be safe for concurrent use by multiple goroutines.\n// The returned Token must not be modified.\nfunc (ts *robotJWTSource) Token() (*oauth2.Token, error) {\n\treq, err := http.NewRequest(http.MethodGet, \"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresp, err := ts.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Read body before checking status to ensure connection can be reused.\n\tresponseBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response body: %w\", err)\n\t}\n\tresponseString := string(responseBytes)\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"status code %d, body:\\n%s\", resp.StatusCode, responseString)\n\t}\n\n\t// Decode token so we can set the expiry and the client knows when to\n\t// refresh. No need to check the signature here (onprem) as it will be\n\t// checked by the cloud backend.\n\tclaims, err := jws.Decode(responseString)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode: %w\", err)\n\t}\n\treturn &oauth2.Token{\n\t\tTokenType:   \"Bearer\",\n\t\tAccessToken: responseString,\n\t\tExpiry:      time.Unix(claims.Exp, 0),\n\t}, nil\n}\n\nconst (\n\t// Minimum remaining lifetime of JWTs from CreateJWTSource().\n\tjwtMinLifetime = time.Minute\n)\n\n// CreateJWTSource creates an OAuth2 token source for the JWTs signed by the\n// robot's private key.\nfunc CreateJWTSource() oauth2.TokenSource {\n\tts := &robotJWTSource{}\n\treturn oauth2.ReuseTokenSourceWithExpiry(nil, ts, jwtMinLifetime)\n}\n"
  },
  {
    "path": "src/go/pkg/robotauth/robotauth_test.go",
    "content": "package robotauth\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n\t\"testing/quick\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2/jws\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\n// Test keys copied from token-vendor oauth/jwt\nconst testPubKey = `\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTGUksynbWhvZkHNJn8C\n2oXVD400jiK4T0JoyS/SwbBGwFr3OJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sM\ngyld9ZYio7SQiiRV/nwYZittGf9/yfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxX\nvGuYG48IH0kqAQbYBI/0lAV3H5pkdXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmz\nQ9+NmKvXWKATAPax1yYoESaZtc22aCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard\n17gywb46HHGl2XoY+Y5pihwvctsFeZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xD\npwIDAQAB\n-----END PUBLIC KEY-----`\n\nconst testPrivKey = `\n-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAvTGUksynbWhvZkHNJn8C2oXVD400jiK4T0JoyS/SwbBGwFr3\nOJGlPwXCsvAPAzmpTuZpge6T3pnIcO/s97sMgyld9ZYio7SQiiRV/nwYZittGf9/\nyfHSNDJUvT25yhuK2p3UqRCom1a3KljeXbxXvGuYG48IH0kqAQbYBI/0lAV3H5pk\ndXPFZC6PHltC3jySVIOg7qPXrNuxdxmg/gmzQ9+NmKvXWKATAPax1yYoESaZtc22\naCZWouIdJr3baYlfBb4w8stoJPoONuyn4ard17gywb46HHGl2XoY+Y5pihwvctsF\neZXLfYwUmFPfgncQHJ02lCV3+Xyk4AAZy3xDpwIDAQABAoH/bKMLrT/W4/wT+6PN\nKU3FVbWDompywyssqlZ31Q6g9pdCCTIyw0jemlG0ewtdk3yIu8WS0Aku36NudWtP\npvDBPo+CZILRYS9N0AUNXBPl7sUA4OzVdCBnk5FTF1daV7N5CA+ZDXuDVa91fduJ\n1ElSF9+weCKph0170Rsc74G570Q1ypoee/gdhkwwK5aYfTs+Z6fpaEnHaPzcwYkF\n4QTsCshtoGZslmgZt8Tm7sfDDFWD20fmr1s350Ne1I7VYRFiyGbQI+IB+4pc9LSX\n8CHcHIzHidKYTSG6YwpDsNRN/BkQklhsuLnNacMFFddO0IHIS0GlLBJbCRkN3b/n\n/XC5AoGBAPZIN3VCpSEAw6OsM1zL4CBcq2dOb5b87rAeUmSkmW415fuyUNJJBcaf\n1pliCQNeg9RzRDuHOs6BTU9i+fLcbOwSapFzGxzqnv4xmkHbj1Xs52Z+97HvKKld\nxlQ/TF72WGITZVwmQWxJ9Rgx+bi7OirzOtQYoNpFoF5vHgyGrUZ7AoGBAMSosXUk\nuLMzrZjH4Oetp8tq9Udyk7Xkk7booU7I0iPb/Dvadsuc9WZI+LP4R3iWmtLcJOUr\nWyfliCLvbWtF4aW2vo7hvffe19krg/H26WEuBTuQGCZv8B5o8xHSecb7jbrKt9g6\nr8I5kr+2tAZKLC6mtFdJgfSXNO9tveBxe+XFAoGBAIwQljnCJVeXr6wuCygDavv8\nuB6QpTYhsz3GgOVsFzZuwNVcnEp77SUBUnL5JlccMa1pwKx6RB+dufIkQDK22duI\nvcLqy8iuRq4aV7iMvgAIM7I/E2/GrEFma50OQsjfIXTlwwedWifUB+gyw+sjz/kN\nS6/EMfbxEjuixlwpW/JxAoGBAKG5dM44F6hPPFijL0J3XcD8QZ+zCuQPiKZnopgO\nsDmLJF/4Za9Gccze/5/I8sWpXMNBBRptUDZ8HTtVmK8aNdm4cfdAj5/y46EVlxl6\nCyy+0tDLzAB4F4h6mEI0y66mmkRdh1jL0lQwUo1Ua7Gsd68Zqr8JlVSWsJKhtf+I\nc/JdAoGAFCSDby7ByX0W23Su3R28+9lWRSmNG79kLRLzlXsCwXTUTFh/TjAaEKgK\nvwi8dtCSMNnJLCUXGx5cjTndgjTl8Woah0wy9XNNeIUjI8JPxIwXmmjppPKdCBI4\n0ZyqQjgPJvwfY7lxFjE10ypv99QDlEbnwngt6bvSkY+6+DQTUDw=\n-----END RSA PRIVATE KEY-----\n`\n\n// Generate random RobotAuth values to ensure any secret stored in the k8s\n// secret can be retrieved without modification to the value\nfunc TestK8sSecretLoadStoreRoundtrip(t *testing.T) {\n\t// FWIW, this should require a better generator to get valid k8s\n\t// namespace names generated, and create the namespace before\n\t// we try to store the secret in it.\n\t// I can only assume the fake server is super lenient here.\n\tif err := quick.Check(func(a RobotAuth, ns string) bool {\n\t\tcs := fake.NewSimpleClientset()\n\n\t\tif err := a.StoreInK8sSecret(context.TODO(), cs, ns); err != nil {\n\t\t\tt.Errorf(\"Failed to store k8s secret: %v\", err)\n\t\t}\n\n\t\tl, err := LoadFromK8sSecret(context.TODO(), cs, ns)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Failed to read k8s secret: %v\", err)\n\t\t}\n\n\t\treturn reflect.DeepEqual(&a, l)\n\t}, nil); err != nil {\n\t\tt.Errorf(\"Failed to check roundtrip of Store/Load K8sSecret: %v\", err)\n\t}\n}\n\nfunc TestCreateJWT(t *testing.T) {\n\ta := RobotAuth{\n\t\tPrivateKey: []byte(testPrivKey),\n\t}\n\n\tjwtk, err := a.CreateJWT(context.TODO(), time.Minute*10)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to create JWT: %v\", err)\n\t}\n\n\tp, _ := pem.Decode([]byte(testPubKey))\n\tif p == nil {\n\t\tt.Fatalf(\"Failed to pem decode pubkey\")\n\t}\n\tparsedKey, err := x509.ParsePKIXPublicKey(p.Bytes)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to decode public key: %v\", err)\n\t}\n\tparsed, ok := parsedKey.(*rsa.PublicKey)\n\tif !ok {\n\t\tt.Fatalf(\"Failed to cast public key to rsa\")\n\t}\n\n\tif err := jws.Verify(jwtk, parsed); err != nil {\n\t\tt.Errorf(\"Failed to validate created JWT: %v\", err)\n\t}\n}\n\ntype mockRoundTripper struct {\n\tresponse *http.Response\n}\n\nfunc (rt *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn rt.response, nil\n}\n\nfunc makeTokenResponse(token string) *http.Response {\n\trecorder := httptest.NewRecorder()\n\trecorder.Header().Add(\"Content-Type\", \"application/json\")\n\trecorder.WriteString(token)\n\treturn recorder.Result()\n}\n\nconst (\n\t// unsigned token with known fields, check/generate with https://jwt.io and\n\t// a throwaway private key:\n\t//   ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key\n\ttestToken = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJyb2JvdC1yb2JvdC1zaW0iLCJhdWQiOiJodHRwczovL3d3dy5lbmRwb2ludHMubXktdGVzdC1wcm9qZWN0LmNsb3VkLmdvb2cvYXBpcy9jb3JlLnRva2VuLXZlbmRvci92MS90b2tlbi5vYXV0aDIiLCJleHAiOjE3NDE2MTU3MjEsImlhdCI6MTc0MTYxNDgxMSwic3ViIjoicm9ib3Qtc2ltIiwicHJuIjoicm9ib3Qtc2ltIn0.\"\n\n\ttextTokenExpiryUnix = 1741615721\n)\n\nfunc TestCreateJWTSource(t *testing.T) {\n\tmockRT := &mockRoundTripper{}\n\tts := &robotJWTSource{\n\t\tclient: http.Client{\n\t\t\tTransport: mockRT,\n\t\t},\n\t}\n\n\tmockRT.response = makeTokenResponse(testToken)\n\ttoken, err := ts.Token()\n\tif err != nil {\n\t\tt.Fatalf(\"ts.Token() failed unexpectedly: %v\", err)\n\t}\n\n\tif want := \"Bearer\"; token.TokenType != want {\n\t\tt.Errorf(\"token.TokenType = %q, want %q\", token.TokenType, want)\n\t}\n\tif want := time.Unix(textTokenExpiryUnix, 0); token.Expiry != want {\n\t\tt.Errorf(\"token.Expiry = %v, want %v\", token.Expiry, want)\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/setup/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\", \"gomock\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"setupcommon.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/setup\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/robotauth:go_default_library\",\n        \"//src/go/pkg/setup/util:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_client_go//dynamic:go_default_library\",\n        \"@org_golang_x_crypto//ssh/terminal:go_default_library\",\n    ],\n)\n\ngomock(\n    name = \"mock_factory\",\n    out = \"mock_factory_test.go\",\n    interfaces = [\"Factory\"],\n    library = \"//src/go/pkg/setup/util:go_default_library\",\n    package = \"setup\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"small\",\n    srcs = [\n        \"mock_factory_test.go\",\n        \"setupcommon_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"//src/go/pkg/setup/util:go_default_library\",  # keep\n        \"@com_github_golang_mock//gomock:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/setup/setupcommon.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage setup\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/robotauth\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/setup/util\"\n\n\t\"golang.org/x/crypto/ssh/terminal\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/client-go/dynamic\"\n)\n\n// GetRobotName returns a valid robot name or an error. If the robotName parameter\n// is non-empty, it checks if it is valid. If it is an empty string, the user is\n// prompted to select a robot.\nfunc GetRobotName(ctx context.Context, f util.Factory, client dynamic.ResourceInterface, robotName string) (string, error) {\n\tif robotName == \"\" {\n\t\texitIfNotRunningInTerminal(\"ERROR: --robot-name not specified\")\n\n\t\trobots, err := client.List(ctx, metav1.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\trobotName, err := selectRobot(f, robots.Items)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn robotName, nil\n\t}\n\t_, err := client.Get(ctx, robotName, metav1.GetOptions{})\n\tif err != nil {\n\t\tif s, ok := err.(*apierrors.StatusError); ok && s.ErrStatus.Reason == metav1.StatusReasonNotFound {\n\t\t\treturn \"\", fmt.Errorf(\"Robot %v not found.\", robotName)\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn robotName, nil\n}\n\n// exitIfNotRunningInTerminal checks if stdin is connected to a terminal. If\n// not, it prints the given message and exits.\nfunc exitIfNotRunningInTerminal(message ...interface{}) {\n\tif !terminal.IsTerminal(int(os.Stdin.Fd())) {\n\t\tfmt.Fprintln(os.Stderr, message...)\n\t\tos.Exit(1)\n\t}\n}\n\n// Ask the user to select the robot from a list. Saves name to disk after\n// selection.\nfunc selectRobot(f util.Factory, robots []unstructured.Unstructured) (string, error) {\n\tfmt.Printf(\"  # %-20v %-10v %-16v\\n\", \"Name\", \"Type\", \"Create Time\")\n\tfor i, robot := range robots {\n\t\tspec, ok := robot.Object[\"spec\"].(map[string]interface{})\n\t\tif !ok {\n\t\t\tslog.Warn(\"unmarshaling robot failed: spec is not a map\")\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"%3v %-20v %-10v %v\\n\", i+1, robot.GetName(), spec[\"type\"], robot.GetCreationTimestamp().String())\n\t}\n\n\tfmt.Print(\"Select robot: \")\n\tvar ix int\n\tfor {\n\t\tvar err error\n\t\tix, err = f.ScanInt()\n\t\tif err == nil && 1 <= ix && ix <= len(robots) {\n\t\t\tbreak\n\t\t}\n\t\tfmt.Printf(\"Please enter a number (1-%v): \", len(robots))\n\t}\n\treturn robots[ix-1].GetName(), nil\n}\n\nfunc newExponentialBackoff(initialInterval time.Duration, multiplier float64, retries uint64) backoff.BackOff {\n\texponentialBackoff := backoff.ExponentialBackOff{\n\t\tInitialInterval: initialInterval,\n\t\tMultiplier:      multiplier,\n\t\tClock:           backoff.SystemClock,\n\t}\n\texponentialBackoff.Reset()\n\treturn backoff.WithMaxRetries(&exponentialBackoff, retries)\n}\n\n// WaitForDNS manually resolves the domain name with retries to give a better\n// error in the case of failure. This is useful to catch errors during first\n// interaction with the cluster and cloud-project,\nfunc WaitForDNS(domain string, retries uint64) error {\n\tslog.Info(\"DNS lookup\", slog.String(\"Domain\", domain))\n\n\tif err := backoff.RetryNotify(\n\t\tfunc() error {\n\t\t\tips, err := net.LookupIP(domain)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Check that the results contain an ipv4 addr. Initially, coredns may only\n\t\t\t// return ipv6 addresses in which case helm will fail.\n\t\t\tfor _, ip := range ips {\n\t\t\t\tif ip.To4() != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn fmt.Errorf(\"IP not found\")\n\t\t},\n\t\tnewExponentialBackoff(time.Second, 2, retries),\n\t\tfunc(_ error, _ time.Duration) {\n\t\t\tslog.Info(\"... Retry dns\", slog.String(\"Domain\", domain))\n\t\t},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"DNS lookup for %q failed: %w\", domain, err)\n\t}\n\n\treturn nil\n}\n\n// WaitForService tests a given cloud endpoint with a HEAD request a few times.\n// This lets us wait for the service to be available or error with a better\n// message.\nfunc WaitForService(client *http.Client, url string, retries uint64) error {\n\tslog.Info(\"Service probe\", slog.String(\"URL\", url))\n\n\tif err := backoff.RetryNotify(\n\t\tfunc() error {\n\t\t\t_, err := client.Head(url)\n\t\t\treturn err\n\t\t},\n\t\tnewExponentialBackoff(time.Second, 2, retries),\n\t\tfunc(_ error, _ time.Duration) {\n\t\t\tslog.Info(\"... Retry service\", slog.String(\"URL\", url))\n\t\t},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"service probe for %q failed: %w\", url, err)\n\t}\n\n\treturn nil\n}\n\n// PublishCredentialsToCloud registers a public-key in the cloud under the ID\n// given as part of the RobotAuth struct.\nfunc PublishCredentialsToCloud(client *http.Client, auth *robotauth.RobotAuth, retries uint64) error {\n\tif len(auth.PrivateKey) == 0 {\n\t\treturn fmt.Errorf(\"Missing key in given auth object\")\n\t}\n\tif err := isKeyRegistryAvailable(auth, client, retries); err != nil {\n\t\treturn fmt.Errorf(\"Failed to connect to cloud key registry: %w\", err)\n\t}\n\tif err := publishPublicKeyToCloudRegistry(auth, client); err != nil {\n\t\treturn fmt.Errorf(\"Failed to register key with cloud key registry: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc isKeyRegistryAvailable(auth *robotauth.RobotAuth, client *http.Client, retries uint64) error {\n\t// Make sure the cloud cluster take requests\n\turl := fmt.Sprintf(\"https://%s/apis/core.token-vendor/v1/public-key.read\", auth.Domain)\n\tif err := WaitForService(client, url, retries); err != nil {\n\t\treturn fmt.Errorf(\"Failed to connect to the cloud cluster: %w. Please retry in 5 minutes.\", err)\n\t}\n\treturn nil\n}\n\nfunc publishPublicKeyToCloudRegistry(auth *robotauth.RobotAuth, client *http.Client) error {\n\tpubKey, err := getPublicKey(auth.PrivateKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tslog.Info(\"Publishing the robot's public key to cloud key registry\")\n\n\turl := fmt.Sprintf(\n\t\t\"https://%s/apis/core.token-vendor/v1/public-key.publish?device-id=%s\",\n\t\tauth.Domain,\n\t\tauth.PublicKeyRegistryId)\n\n\tresponse, err := client.Post(\n\t\turl, \"application/x-pem-file\", strings.NewReader(string(pubKey)))\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"publishing the token failed: %w\", err)\n\t} else if response.StatusCode != http.StatusOK {\n\t\tresponseBody := new(bytes.Buffer)\n\t\tresponseBody.ReadFrom(response.Body)\n\t\treturn fmt.Errorf(\n\t\t\t\"TokenVendor responded with %d %s: %s\",\n\t\t\tresponse.StatusCode,\n\t\t\tresponse.Status,\n\t\t\tresponseBody.String())\n\t}\n\n\treturn nil\n}\n\nfunc getPublicKey(privateKey []byte) ([]byte, error) {\n\tblock, _ := pem.Decode(privateKey)\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"Private key is not a valid PEM object\")\n\t}\n\tvar rsaKey *rsa.PrivateKey\n\tif block.Type == \"RSA PRIVATE KEY\" {\n\t\tkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trsaKey = key\n\t} else if block.Type == \"PRIVATE KEY\" {\n\t\tkey, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trsaKey = key.(*rsa.PrivateKey)\n\t} else {\n\t\treturn nil, fmt.Errorf(\"Expected a private key, got %s\", block.Type)\n\t}\n\tpubKey, err := x509.MarshalPKIXPublicKey(&rsaKey.PublicKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn pem.EncodeToMemory(&pem.Block{\n\t\tType:  \"PUBLIC KEY\",\n\t\tBytes: pubKey,\n\t}), nil\n}\n\n// megeMaps returns `base` with `additions` added on top.\n// I.e., if the same key is present in both maps, the one from `additions` wins.\nfunc mergeMaps(base, additions map[string]string) map[string]string {\n\tresult := make(map[string]string)\n\tfor k, v := range base {\n\t\tresult[k] = v\n\t}\n\tfor k, v := range additions {\n\t\tresult[k] = v\n\t}\n\treturn result\n}\n\n// CreateOrUpdateRobot adds a new robot-cr or updates an existing one.\nfunc CreateOrUpdateRobot(ctx context.Context, client dynamic.ResourceInterface, robotName, robotType, project string, labels map[string]string, annotations map[string]string) error {\n\trobot, err := client.Get(ctx, robotName, metav1.GetOptions{})\n\tif err != nil {\n\t\tif s, ok := err.(*apierrors.StatusError); ok && s.ErrStatus.Reason == metav1.StatusReasonNotFound {\n\t\t\trobot := &unstructured.Unstructured{}\n\t\t\trobot.SetKind(\"Robot\")\n\t\t\trobot.SetAPIVersion(\"registry.cloudrobotics.com/v1alpha1\")\n\t\t\trobot.SetName(robotName)\n\n\t\t\trobot.SetLabels(labels)\n\t\t\trobot.SetAnnotations(annotations)\n\t\t\trobot.Object[\"spec\"] = map[string]interface{}{\n\t\t\t\t\"type\":    robotType,\n\t\t\t\t\"project\": project,\n\t\t\t}\n\t\t\trobot.Object[\"status\"] = make(map[string]interface{})\n\t\t\t_, err := client.Create(ctx, robot, metav1.CreateOptions{})\n\t\t\treturn err\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"Failed to get robot %v: %w\", robotName, err)\n\t\t}\n\t}\n\n\t// A robot with the same name already exists.\n\trobot.SetLabels(mergeMaps(robot.GetLabels(), labels))\n\trobot.SetAnnotations(mergeMaps(robot.GetAnnotations(), annotations))\n\tspec, ok := robot.Object[\"spec\"].(map[string]interface{})\n\tif !ok {\n\t\treturn fmt.Errorf(\"unmarshaling robot failed: spec is not a map\")\n\t}\n\tspec[\"type\"] = robotType\n\tspec[\"project\"] = project\n\t_, err = client.Update(ctx, robot, metav1.UpdateOptions{})\n\treturn err\n}\n"
  },
  {
    "path": "src/go/pkg/setup/setupcommon_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage setup\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc TestSelectRobot(t *testing.T) {\n\tmockCtrl := gomock.NewController(t)\n\tdefer mockCtrl.Finish()\n\n\tmockFactory := NewMockFactory(mockCtrl)\n\n\trobots := []unstructured.Unstructured{\n\t\t{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"registry.cloudrobotics.com/v1alpha1\",\n\t\t\t\t\"kind\":       \"Robot\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"namespace\": \"default\",\n\t\t\t\t\t\"name\":      \"ro-1234\",\n\t\t\t\t\t\"labels\": map[string]interface{}{\n\t\t\t\t\t\t\"cloudrobotics.com/robot-name\": \"ro-1234\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"type\": \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmockFactory.EXPECT().ScanInt().Return(1, nil).Times(1)\n\n\tid, err := selectRobot(mockFactory, robots)\n\tif id != \"ro-1234\" || err != nil {\n\t\tt.Errorf(\"selectRobot(mockFactory, oneRobot) = %v, %v want ro-1234, nil\", id, err)\n\t}\n}\n\nfunc TestWaitForService_OkIfServiceResponds(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))\n\tdefer server.Close()\n\n\terr := WaitForService(server.Client(), server.URL, 1)\n\tif err != nil {\n\t\tt.Errorf(\"WaitForService returned error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/setup/util/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"factory.go\",\n        \"fake.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/setup/util\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/go/pkg/setup/util/factory.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage util\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Factory interface {\n\tScanInt() (int, error)\n\tGetNetworkInterfaceIP(string) (string, error)\n}\n\ntype DefaultFactory struct{}\n\nfunc NewFactory() *DefaultFactory {\n\treturn &DefaultFactory{}\n}\n\n// Read stdin up to the next space and convert to an int.\nfunc (f *DefaultFactory) ScanInt() (int, error) {\n\tvar s string\n\t_, err := fmt.Scan(&s)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\ti, err := strconv.Atoi(s)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn i, nil\n}\n\n// GetNetworkInterfaceIP returns the IP address of the first local network interface whose name\n// starts with namePrefix\nfunc (f *DefaultFactory) GetNetworkInterfaceIP(namePrefix string) (string, error) {\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfor _, i := range ifaces {\n\t\tif strings.HasPrefix(i.Name, namePrefix) {\n\t\t\taddrs, err := i.Addrs()\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tfor _, address := range addrs {\n\t\t\t\tif ipnet, ok := address.(*net.IPNet); ok {\n\t\t\t\t\tif ipnet.IP.To4() != nil {\n\t\t\t\t\t\treturn ipnet.IP.String(), nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"Could not look up IP of interface with prefix: %v\", namePrefix)\n}\n"
  },
  {
    "path": "src/go/pkg/setup/util/fake.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage util\n\ntype TestFactory struct {\n\tScanIntImpl func() (int, error)\n}\n\nfunc NewTestFactory() *TestFactory {\n\treturn &TestFactory{}\n}\n\nfunc (f *TestFactory) ScanInt() (int, error) {\n\treturn f.ScanIntImpl()\n}\n"
  },
  {
    "path": "src/go/pkg/synk/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"interface.go\",\n        \"sort.go\",\n        \"synk.go\",\n    ],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/pkg/synk\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/meta:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/jsonmergepatch:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/mergepatch:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/strategicpatch:go_default_library\",\n        \"@io_k8s_client_go//discovery:go_default_library\",\n        \"@io_k8s_client_go//discovery/cached:go_default_library\",\n        \"@io_k8s_client_go//dynamic:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/scheme:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//restmapper:go_default_library\",\n        \"@io_opencensus_go//trace:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\n        \"sort_test.go\",\n        \"synk_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/meta/testrestmapper:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/types:go_default_library\",\n        \"@io_k8s_client_go//discovery:go_default_library\",\n        \"@io_k8s_client_go//dynamic/fake:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/scheme:go_default_library\",\n        \"@io_k8s_client_go//testing:go_default_library\",\n        \"@io_k8s_sigs_yaml//:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/pkg/synk/interface.go",
    "content": "package synk\n\nimport (\n\t\"context\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\ntype Interface interface {\n\tInit() error\n\tDelete(ctx context.Context, name string) error\n\tApply(ctx context.Context, name string, opts *ApplyOptions, resources ...*unstructured.Unstructured) (*apps.ResourceSet, error)\n}\n"
  },
  {
    "path": "src/go/pkg/synk/sort.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage synk\n\nimport (\n\t\"fmt\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\n// gvknn is only used to unify the less functions.\ntype gvknn struct {\n\tpriority  int\n\tgroup     string\n\tversion   string\n\tkind      string\n\tnamespace string\n\tname      string\n}\n\nfunc newGvknn(group, version, kind, namespace, name string) *gvknn {\n\tp := 999\n\tswitch kind {\n\tcase \"Namespace\":\n\t\t// Adding resources to a non existing namespace removes them. So namespaces\n\t\t// need to go early.\n\t\tp = 1\n\tcase \"ServiceAccount\":\n\t\tp = 2\n\tcase \"Secret\":\n\t\t// We need ServiceAccount to be before Secret. The token controller removes\n\t\t// Secrets with non existing ServiceAccount.\n\t\tp = 3\n\t}\n\treturn &gvknn{p, group, version, kind, namespace, name}\n}\n\nfunc less(l, r *gvknn) bool {\n\tls := fmt.Sprintf(\"%03d/%s/%s/%s/%s/%s\", l.priority, l.group, l.version, l.kind, l.namespace, l.name)\n\trs := fmt.Sprintf(\"%03d/%s/%s/%s/%s/%s\", r.priority, r.group, r.version, r.kind, r.namespace, r.name)\n\treturn ls < rs\n}\n\nfunc gvknnUnstructured(u *unstructured.Unstructured) *gvknn {\n\tgvk := u.GroupVersionKind()\n\treturn newGvknn(gvk.Group, gvk.Version, gvk.Kind, u.GetNamespace(), u.GetName())\n}\n\nfunc gvknnRSpecG(r *apps.ResourceSetSpecGroup) *gvknn {\n\treturn newGvknn(r.Group, r.Version, r.Kind, \"\", \"\")\n}\n\nfunc gvknnRStatusG(r *apps.ResourceSetStatusGroup) *gvknn {\n\treturn newGvknn(r.Group, r.Version, r.Kind, \"\", \"\")\n}\n\nfunc lessUnstructured(l, r *unstructured.Unstructured) bool {\n\treturn less(gvknnUnstructured(l), gvknnUnstructured(r))\n}\n\nfunc lessResourceSetSpecGroup(l, r *apps.ResourceSetSpecGroup) bool {\n\treturn less(gvknnRSpecG(l), gvknnRSpecG(r))\n}\n\nfunc lessResourceSetStatusGroup(l, r *apps.ResourceSetStatusGroup) bool {\n\treturn less(gvknnRStatusG(l), gvknnRStatusG(r))\n}\n"
  },
  {
    "path": "src/go/pkg/synk/sort_test.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage synk\n\nimport (\n\t\"testing\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n)\n\nfunc TestLessResourceSetStatusGroup(t *testing.T) {\n\ta := &apps.ResourceSetStatusGroup{\n\t\tGroup:   \"g\",\n\t\tVersion: \"v\",\n\t\tKind:    \"k\",\n\t}\n\tb := &apps.ResourceSetStatusGroup{\n\t\tGroup:   \"g\",\n\t\tVersion: \"v\",\n\t\tKind:    \"l\",\n\t}\n\tif !lessResourceSetStatusGroup(a, b) {\n\t\tt.Errorf(\"expected a < b\")\n\t}\n\tif lessResourceSetStatusGroup(b, a) {\n\t\tt.Errorf(\"expected b >= a\")\n\t}\n}\n\nfunc TestLessResourceSetSpecGroup(t *testing.T) {\n\ta := &apps.ResourceSetSpecGroup{\n\t\tGroup:   \"g\",\n\t\tVersion: \"v\",\n\t\tKind:    \"k\",\n\t}\n\tb := &apps.ResourceSetSpecGroup{\n\t\tGroup:   \"g\",\n\t\tVersion: \"v\",\n\t\tKind:    \"l\",\n\t}\n\tif !lessResourceSetSpecGroup(a, b) {\n\t\tt.Errorf(\"expected a < b\")\n\t}\n\tif lessResourceSetSpecGroup(b, a) {\n\t\tt.Errorf(\"expected b >= a\")\n\t}\n}\n\nfunc TestLessUnstructured(t *testing.T) {\n\ta := newUnstructured(\"v1\", \"Secret\", \"ns1\", \"pod\")\n\tb := newUnstructured(\"v1\", \"Secret\", \"ns1\", \"poe\")\n\tif !lessUnstructured(a, b) {\n\t\tt.Errorf(\"expected a < b\")\n\t}\n\tif lessUnstructured(b, a) {\n\t\tt.Errorf(\"expected b >= a\")\n\t}\n}\n\nfunc TestLess(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\ta, b *gvknn\n\t}{\n\t\t{newGvknn(\"a\", \"v\", \"k\", \"ns\", \"n\"), newGvknn(\"b\", \"v\", \"k\", \"ns\", \"n\")},\n\t\t{newGvknn(\"g\", \"a\", \"k\", \"ns\", \"n\"), newGvknn(\"g\", \"b\", \"k\", \"ns\", \"n\")},\n\t\t{newGvknn(\"g\", \"v\", \"a\", \"ns\", \"n\"), newGvknn(\"g\", \"v\", \"b\", \"ns\", \"n\")},\n\t\t{newGvknn(\"g\", \"v\", \"k\", \"a\", \"n\"), newGvknn(\"g\", \"v\", \"k\", \"b\", \"n\")},\n\t\t{newGvknn(\"g\", \"v\", \"k\", \"ns\", \"a\"), newGvknn(\"g\", \"v\", \"k\", \"ns\", \"b\")},\n\t\t{newGvknn(\"g\", \"v\", \"ServiceAccount\", \"ns\", \"a\"), newGvknn(\"g\", \"v\", \"Secret\", \"ns\", \"b\")},\n\t\t{newGvknn(\"g\", \"v\", \"Secret\", \"ns\", \"a\"), newGvknn(\"g\", \"v\", \"ServiceAccount2\", \"ns\", \"b\")},\n\t} {\n\t\tif !less(tc.a, tc.b) {\n\t\t\tt.Errorf(\"expected a (%v) < b (%v)\", tc.a, tc.b)\n\t\t}\n\t\tif less(tc.b, tc.a) {\n\t\t\tt.Errorf(\"expected b (%v) >= a (%v)\", tc.b, tc.a)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/go/pkg/synk/synk.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package synk contains functionality to synchronize a batch of resources\n// with a cluster while correctly handling CRDs and deletions.\npackage synk\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\t\"github.com/pkg/errors\"\n\t\"go.opencensus.io/trace\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapiextensions \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/jsonmergepatch\"\n\t\"k8s.io/apimachinery/pkg/util/mergepatch\"\n\t\"k8s.io/apimachinery/pkg/util/strategicpatch\"\n\t\"k8s.io/client-go/discovery\"\n\tcacheddiscovery \"k8s.io/client-go/discovery/cached\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/restmapper\"\n)\n\n// src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go\nconst totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB\n\n// Synk allows to synchronize sets of resources with a fixed cluster.\ntype Synk struct {\n\tdiscovery   discovery.CachedDiscoveryInterface\n\tclient      dynamic.Interface\n\tmapper      meta.RESTMapper\n\tresetMapper func()\n}\n\n// New returns a new Synk object that acts against the cluster for the given configuration.\nfunc New(client dynamic.Interface, discovery discovery.CachedDiscoveryInterface) *Synk {\n\ts := &Synk{\n\t\tdiscovery: discovery,\n\t\tclient:    client,\n\t}\n\t// Store reset function seperately to allow reasonable tests.\n\tm := restmapper.NewDeferredDiscoveryRESTMapper(discovery)\n\ts.mapper = m\n\ts.resetMapper = m.Reset\n\n\treturn s\n}\n\nfunc NewForConfig(cfg *rest.Config) (*Synk, error) {\n\tclient, err := dynamic.NewForConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdiscovery, err := discovery.NewDiscoveryClientForConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcachedDiscovery := cacheddiscovery.NewMemCacheClient(discovery)\n\t// Without initial invalidation all calls will fail.\n\tcachedDiscovery.Invalidate()\n\n\treturn New(client, cachedDiscovery), nil\n}\n\n// TODO: determine options that allow us to be semantically compatible with\n// vanilla kubectl apply.\ntype ApplyOptions struct {\n\tname    string\n\tversion int32\n\n\t// Namespace that's set for all namespaced resources that have no\n\t// other namespace set yet.\n\tNamespace string\n\t// EnforceNamespace causes apply to fail if a resource has a namespace set\n\t// that's different from Namespace.\n\tEnforceNamespace bool\n\n\t// Log functions to report progress and failures while applying resources.\n\tLog func(r *unstructured.Unstructured, a apps.ResourceAction, status, msg string)\n}\n\nconst (\n\tStatusSuccess = \"success\"\n\tStatusFailure = \"failure\"\n)\n\nfunc (o *ApplyOptions) logf(r *unstructured.Unstructured, action apps.ResourceAction, msg string, args ...interface{}) {\n\tif o.Log != nil {\n\t\to.Log(r, action, StatusSuccess, fmt.Sprintf(msg, args...))\n\t}\n}\n\nfunc (o *ApplyOptions) errorf(r *unstructured.Unstructured, action apps.ResourceAction, msg string, args ...interface{}) {\n\tif o.Log != nil {\n\t\to.Log(r, action, StatusFailure, fmt.Sprintf(msg, args...))\n\t}\n}\n\n// Init installs the ResourceSet CRD into the cluster and waits for\n// it to become available.\n// It does not need to be called before each use of Synk.\nfunc (s *Synk) Init() error {\n\tvTrue := true\n\tcrd := &apiextensions.CustomResourceDefinition{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: \"apiextensions.k8s.io/v1\",\n\t\t\tKind:       \"CustomResourceDefinition\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"resourcesets.apps.cloudrobotics.com\",\n\t\t},\n\t\tSpec: apiextensions.CustomResourceDefinitionSpec{\n\t\t\tGroup: \"apps.cloudrobotics.com\",\n\t\t\tNames: apiextensions.CustomResourceDefinitionNames{\n\t\t\t\tKind:     \"ResourceSet\",\n\t\t\t\tPlural:   \"resourcesets\",\n\t\t\t\tSingular: \"resourceset\",\n\t\t\t},\n\t\t\tScope: apiextensions.ClusterScoped,\n\t\t\tVersions: []apiextensions.CustomResourceDefinitionVersion{{\n\t\t\t\tName:    \"v1alpha1\",\n\t\t\t\tServed:  true,\n\t\t\t\tStorage: true,\n\t\t\t\t// TODO(ensonic): replace with the actual schema\n\t\t\t\tSchema: &apiextensions.CustomResourceValidation{\n\t\t\t\t\tOpenAPIV3Schema: &apiextensions.JSONSchemaProps{\n\t\t\t\t\t\tType: \"object\",\n\t\t\t\t\t\tProperties: map[string]apiextensions.JSONSchemaProps{\n\t\t\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\t\tType:                   \"object\",\n\t\t\t\t\t\t\t\tXPreserveUnknownFields: &vTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": {\n\t\t\t\t\t\t\t\tType:                   \"object\",\n\t\t\t\t\t\t\t\tXPreserveUnknownFields: &vTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}},\n\t\t},\n\t}\n\tvar u unstructured.Unstructured\n\tif err := convert(crd, &u); err != nil {\n\t\treturn err\n\t}\n\tif _, err := s.applyOne(context.Background(), &u, nil); err != nil {\n\t\treturn errors.Wrap(err, \"create ResourceSet CRD\")\n\t}\n\n\terr := backoff.Retry(\n\t\tfunc() error {\n\t\t\ts.discovery.Invalidate()\n\t\t\tok, err := s.crdAvailable(&u)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"crd not available\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(2*time.Second), 60),\n\t)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"wait for ResourceSet CRD\")\n\t}\n\n\treturn nil\n}\n\n// Delete removes the resources that are part of the ResourceSet specified by\n// 'name'. It uses so-called \"foreground cascading deletion\", which means that:\n//\n// - it returns after marking the ResourceSet for deletion, but before the\n//\n//\tresources have been deleted\n//\n// - the ResourceSet will not be deleted until all resources have been deleted\n//\n// This ensures that if a new ResourceSet is created before all resources have\n// been deleted, it will have a higher version number.\nfunc (s *Synk) Delete(ctx context.Context, name string) error {\n\tpolicy := metav1.DeletePropagationForeground\n\tdeleteOpts := metav1.DeleteOptions{PropagationPolicy: &policy}\n\treturn s.client.Resource(resourceSetGVR).DeleteCollection(ctx, deleteOpts, metav1.ListOptions{\n\t\tLabelSelector: fmt.Sprintf(\"name=%s\", name),\n\t})\n}\n\n// Apply installs or updates the ResourceSet specified by 'name'.\nfunc (s *Synk) Apply(\n\tctx context.Context,\n\tname string,\n\topts *ApplyOptions,\n\tresources ...*unstructured.Unstructured,\n) (*apps.ResourceSet, error) {\n\tif opts == nil {\n\t\topts = &ApplyOptions{}\n\t}\n\topts.name = name\n\n\t// applyAll() updates the resources in place. To avoid modifying the\n\t// caller's slice, copy the resources first.\n\tresources = append([]*unstructured.Unstructured(nil), resources...)\n\tfor i, r := range resources {\n\t\tresources[i] = r.DeepCopy()\n\t}\n\n\trs, resources, err := s.initialize(ctx, opts, resources...)\n\tif err != nil {\n\t\treturn rs, err\n\t}\n\tresults, applyErr := s.applyAll(ctx, rs, opts, resources...)\n\tdefer func() {\n\t\t// We always want to clean up old failed ResourceSets. There is no reason\n\t\t// to keep multiple failed ones around. But a failure in the cleanup is not\n\t\t// critical. So we only log it.\n\t\tif err := s.deleteFailedResourceSets(ctx, opts.name, opts.version); err != nil {\n\t\t\tslog.Warn(\"Failed to remove failed ResourceSets\", slog.Any(\"Name\", opts.name), slog.Any(\"Error\", err))\n\t\t}\n\t}()\n\n\tif err := s.updateResourceSetStatus(ctx, rs, results); err != nil {\n\t\treturn rs, err\n\t}\n\tif applyErr == nil {\n\t\tif err := s.deleteResourceSets(ctx, opts.name, opts.version); err != nil {\n\t\t\treturn rs, err\n\t\t}\n\t}\n\treturn rs, applyErr\n}\n\ntype transientErr struct {\n\terror\n}\n\n// IsTransientErr returns true if the error may resolve by retrying the operation.\nfunc IsTransientErr(err error) bool {\n\t// Either a custom error is specifically wrapped in transientErr or the innermost\n\t// error is a known transient Kubernetes API error.\n\t_, ok1 := err.(transientErr)\n\t_, ok2 := err.(*transientErr)\n\tif ok1 || ok2 {\n\t\treturn true\n\t}\n\terr = errors.Cause(err)\n\tswitch {\n\t// May happen on resourceVersion mismatches or patch conflicts.\n\tcase k8serrors.IsConflict(err):\n\tcase k8serrors.IsResourceExpired(err):\n\t// May happen if a created object has already been created.\n\tcase k8serrors.IsAlreadyExists(err):\n\t// May happen if a patched resource has already been deleted.\n\tcase k8serrors.IsNotFound(err):\n\tcase k8serrors.IsGone(err):\n\t// Server-side transient errors.\n\tcase k8serrors.IsInternalError(err):\n\tcase k8serrors.IsServerTimeout(err):\n\tcase k8serrors.IsTimeout(err):\n\tcase k8serrors.IsTooManyRequests(err):\n\tcase k8serrors.IsServiceUnavailable(err):\n\t// May happen shortly after CRD creation.\n\tcase discovery.IsGroupDiscoveryFailedError(err):\n\t// May happen if a chart is deleted and immediately recreated.\n\t// https://github.com/kubernetes/kubernetes/blob/d2a081c8e14e21e28fe5bdfa38a817ef9c0bb8e3/staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go#L173\n\tcase strings.Contains(err.Error(), \"unable to create new content in namespace\"):\n\t// Note: Golang switch cases don't fall through, so we only return\n\t// false in the default case.\n\tdefault:\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s *Synk) applyAll(\n\tctx context.Context,\n\trs *apps.ResourceSet,\n\topts *ApplyOptions,\n\tresources ...*unstructured.Unstructured,\n) (applyResults, error) {\n\tresults := applyResults{}\n\n\tcrds, regulars := separateCRDsFromResources(resources)\n\n\t// Insert CRDs and wait for them to become available.\n\tfor _, crd := range crds {\n\t\t// CRDs must never be replaced as deleting them will delete\n\t\t// all its current instances. Update conflicts must be resolved manually.\n\t\taction, err := s.applyOne(ctx, crd, rs)\n\t\tif err != nil {\n\t\t\topts.errorf(crd, action, \"failed to apply: %s\", err)\n\t\t} else {\n\t\t\topts.logf(crd, action, \"applied successfully\")\n\t\t}\n\t\tresults.set(crd, action, err)\n\t}\n\terr := backoff.Retry(\n\t\tfunc() error {\n\t\t\ts.discovery.Invalidate()\n\t\t\tfor _, crd := range crds {\n\t\t\t\tif ok, err := s.crdAvailable(crd); err != nil {\n\t\t\t\t\treturn backoff.Permanent(err)\n\t\t\t\t} else if !ok {\n\t\t\t\t\treturn fmt.Errorf(\"crd not yet available: %q\", crd.GetName())\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(2*time.Second), 60),\n\t)\n\tif err != nil {\n\t\treturn results, errors.Wrap(err, \"wait for CRDs\")\n\t}\n\t// Reset all discovery and mapping once again.\n\ts.resetMapper()\n\n\t// Try applying until the errors stay the same between iterations. Put in\n\t// an upper bound just in case of flapping errors.\n\tprevFailures := 0\n\n\tfor i := 0; i < 10; i++ {\n\t\tcurFailures := 0\n\n\t\tfor _, r := range regulars {\n\t\t\t// Don't retry resources that were applied successfully\n\t\t\t// in the first iteration.\n\t\t\tif i > 0 && !results.failed(r) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Attach the ResourceSet as owner. CRDs are exempt since\n\t\t\t// the risk of unintended deletion of all its instances is too high.\n\t\t\tsetOwnerRef(r, rs)\n\t\t\taction, err := s.applyOne(ctx, r, rs)\n\t\t\tif err != nil {\n\t\t\t\tcurFailures++\n\t\t\t\topts.errorf(r, action, \"failed to apply, may retry: %s\", err)\n\t\t\t} else {\n\t\t\t\topts.logf(r, action, \"applied successfully\")\n\t\t\t}\n\t\t\tresults.set(r, action, err)\n\t\t}\n\t\tif curFailures == 0 || curFailures == prevFailures {\n\t\t\tbreak\n\t\t}\n\t\tprevFailures = curFailures\n\t}\n\t// The overall error we return is a transient error if all resource errors\n\t// are transient. If there's at least one permanent failure, retrying\n\t// will never make Apply overall successful.\n\tallTransient := true\n\tnumErrors := 0\n\tvar firstFailure *applyResult\n\tfor _, r := range results {\n\t\tif r.err != nil {\n\t\t\tif !IsTransientErr(r.err) {\n\t\t\t\tallTransient = false\n\t\t\t}\n\t\t\tif firstFailure == nil {\n\t\t\t\tfirstFailure = r\n\t\t\t}\n\t\t\tnumErrors++\n\t\t}\n\t}\n\tif numErrors == 0 {\n\t\treturn results, nil\n\t}\n\terr = fmt.Errorf(\"%d/%d resources failed to apply\", numErrors, len(results))\n\tif numErrors == 1 {\n\t\terr = fmt.Errorf(\"%s: %s: %s\", err, resourceKey(firstFailure.resource), firstFailure.err)\n\t} else {\n\t\terr = fmt.Errorf(\"%s, including %s: %s\", err, resourceKey(firstFailure.resource), firstFailure.err)\n\t}\n\tif allTransient {\n\t\terr = transientErr{err}\n\t}\n\treturn results, err\n}\n\n// initialize a new ResourceSet version for the given name and prepare resources\n// for it.\nfunc (s *Synk) initialize(\n\tctx context.Context,\n\topts *ApplyOptions,\n\tresources ...*unstructured.Unstructured,\n) (*apps.ResourceSet, []*unstructured.Unstructured, error) {\n\t// Cleanup and sort resources.\n\tresources = filter(resources, func(r *unstructured.Unstructured) bool {\n\t\treturn !reflect.DeepEqual(*r, unstructured.Unstructured{}) && !isTestResource(r)\n\t})\n\tsortResources(resources)\n\n\tcrds, regulars := separateCRDsFromResources(resources)\n\n\tif err := s.populateNamespaces(ctx, opts.Namespace, crds, regulars...); err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"set default namespaces\")\n\t}\n\t// TODO: consider putting this and other validation as a step after initialize\n\t// so we can give validation errors in batch in the ResourceSet status.\n\tif opts.EnforceNamespace {\n\t\tfor _, r := range regulars {\n\t\t\tif ns := r.GetNamespace(); ns != \"\" && ns != opts.Namespace && ns != \"kube-system\" {\n\t\t\t\treturn nil, nil, errors.Errorf(\"invalid namespace %q on %q, expected %q or \\\"kube-system\\\"\", ns, resourceKey(r), opts.Namespace)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Initialize and create next ResourceSet.\n\tvar err error\n\topts.version, err = s.next(ctx, opts.name)\n\tif err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"get next ResourceSet version\")\n\t}\n\n\tvar rs apps.ResourceSet\n\trs.Name = resourceSetName(opts.name, opts.version)\n\trs.Labels = map[string]string{\"name\": opts.name}\n\n\tgroupedResources := map[schema.GroupVersionKind][]apps.ResourceRef{}\n\tfor _, r := range resources {\n\t\tgvk := r.GroupVersionKind()\n\t\tgroupedResources[gvk] = append(groupedResources[gvk], apps.ResourceRef{\n\t\t\tNamespace: r.GetNamespace(),\n\t\t\tName:      r.GetName(),\n\t\t})\n\t}\n\tfor gvk, res := range groupedResources {\n\t\trs.Spec.Resources = append(rs.Spec.Resources, apps.ResourceSetSpecGroup{\n\t\t\tGroup:   gvk.Group,\n\t\t\tVersion: gvk.Version,\n\t\t\tKind:    gvk.Kind,\n\t\t\tItems:   res,\n\t\t})\n\t}\n\tsort.Slice(rs.Spec.Resources, func(i, j int) bool {\n\t\treturn lessResourceSetSpecGroup(&rs.Spec.Resources[i], &rs.Spec.Resources[j])\n\t})\n\n\trs.Status = apps.ResourceSetStatus{\n\t\tPhase:     apps.ResourceSetPhasePending,\n\t\tStartedAt: metav1.Now(),\n\t}\n\tif err := s.createResourceSet(ctx, &rs); err != nil {\n\t\treturn nil, nil, errors.Wrapf(err, \"create resources object %q\", rs.Name)\n\t}\n\n\treturn &rs, resources, nil\n}\n\n// Set default namespace on all namespaced resources.\nfunc (s *Synk) populateNamespaces(\n\tctx context.Context,\n\tns string,\n\tcrds []*unstructured.Unstructured,\n\tresources ...*unstructured.Unstructured,\n) error {\n\t_, span := trace.StartSpan(ctx, \"Discover server resources\")\n\t// Invalidate is cheap (no noticeable effect on the duration of\n\t// ServerGroupsAndResources) and reduces the frequency of the \"stale\n\t// GroupVersion discovery\" warning.\n\ts.discovery.Invalidate()\n\t_, list, err := s.discovery.ServerGroupsAndResources()\n\tspan.End()\n\n\tif err != nil {\n\t\tif len(list) == 0 {\n\t\t\t// This error is only fatal if it actually fails to discover resources.\n\t\t\t// Otherwise it indicates that the apiserver has cached some bad\n\t\t\t// information about an aggregated API extension.\n\t\t\treturn errors.Wrap(err, \"discover server resources\")\n\t\t}\n\t\tslog.Warn(\"Ignoring error from ServerGroupsAndResources\", ilog.Err(err))\n\t}\n\n\t// We have to consider discoverable resources as well as CRDs that\n\t// will only be added later.\n\tisNamespaced := map[string]bool{}\n\n\tfor _, srvRes := range list {\n\t\tfor _, sr := range srvRes.APIResources {\n\t\t\tisNamespaced[srvRes.GroupVersion+\"/\"+sr.Kind] = sr.Namespaced\n\t\t}\n\t}\n\tfor _, crd := range crds {\n\t\tvar typed apiextensions.CustomResourceDefinition\n\t\tif err := convert(crd, &typed); err != nil {\n\t\t\treturn errors.Wrapf(err, \"invalid CustomResourceDefinition %q\", resourceKey(crd))\n\t\t}\n\t\tfor _, v := range typed.Spec.Versions {\n\t\t\tk := typed.Spec.Group + \"/\" + v.Name + \"/\" + typed.Spec.Names.Kind\n\t\t\tisNamespaced[k] = typed.Spec.Scope != apiextensions.ClusterScoped\n\t\t}\n\t}\n\tfor _, r := range resources {\n\t\tif r.GetNamespace() != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgvk := r.GetAPIVersion() + \"/\" + r.GetKind()\n\t\trIsNamespaced, ok := isNamespaced[gvk]\n\t\tif !ok {\n\t\t\tslog.Warn(\"Neither apiserver nor chart CRDs indicate if resource is Namespaced or Cluster-scoped, assuming Cluster-scoped\",\n\t\t\t\tslog.String(\"Namespace\", ns), slog.String(\"Kind\", gvk))\n\t\t}\n\t\tif rIsNamespaced {\n\t\t\tr.SetNamespace(ns)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc deleteAppliedAnnotation(u *unstructured.Unstructured) {\n\tanns := u.GetAnnotations()\n\tif anns == nil {\n\t\tanns = map[string]string{}\n\t}\n\t// Delete any potential pre-existing annotation.\n\tdelete(anns, corev1.LastAppliedConfigAnnotation)\n\tu.SetAnnotations(anns)\n}\n\nfunc setAppliedAnnotation(u *unstructured.Unstructured) error {\n\tdeleteAppliedAnnotation(u)\n\n\tb, err := u.MarshalJSON()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(b) >= totalAnnotationSizeLimitB {\n\t\treturn errors.Errorf(\"skipping annotation %q for %q: size %d > max. allowed size %d\",\n\t\t\tcorev1.LastAppliedConfigAnnotation, u.GetName(), len(b), totalAnnotationSizeLimitB)\n\t}\n\tanns := u.GetAnnotations()\n\tanns[corev1.LastAppliedConfigAnnotation] = string(b)\n\tu.SetAnnotations(anns)\n\treturn nil\n}\n\nfunc getAppliedAnnotation(u *unstructured.Unstructured) []byte {\n\treturn []byte(u.GetAnnotations()[corev1.LastAppliedConfigAnnotation])\n}\n\n// validateOwnerRefs returns an error if the resource has ResourceSet owners\n// that are not predecessors of name/version.\nfunc validateOwnerRefs(r *unstructured.Unstructured, set *apps.ResourceSet) error {\n\tif set == nil {\n\t\treturn nil\n\t}\n\tname, version, ok := decodeResourceSetName(set.Name)\n\tif !ok {\n\t\treturn errors.Errorf(\"invalid ResourceSet name %q\", set.Name)\n\t}\n\tfor _, or := range r.GetOwnerReferences() {\n\t\tif or.APIVersion != \"apps.cloudrobotics.com/v1alpha1\" || or.Kind != \"ResourceSet\" {\n\t\t\tcontinue\n\t\t}\n\t\tn, v, ok := decodeResourceSetName(or.Name)\n\t\tif !ok {\n\t\t\treturn errors.Errorf(\"ResourceSet owner reference with invalid name %q\", or.Name)\n\t\t}\n\t\tif n != name {\n\t\t\treturn errors.Errorf(\"owned by conflicting ResourceSet object %q\", or.Name)\n\t\t}\n\t\tif v > version {\n\t\t\t// TODO(rodrigoq): should this be transient to cope with concurrent synk runs?\n\t\t\treturn errors.Errorf(\"owned by newer ResourceSet %q > v%d\", or.Name, version)\n\t\t}\n\t}\n\treturn nil\n}\n\n// setOwnerRef sets the ResourceSet as the owner and removers all other ResourceSet\n// owner references.\nfunc setOwnerRef(r *unstructured.Unstructured, set *apps.ResourceSet) {\n\tvar newRefs []metav1.OwnerReference\n\tfor _, or := range r.GetOwnerReferences() {\n\t\tif or.APIVersion != \"apps.cloudrobotics.com/v1alpha1\" || or.Kind != \"ResourceSet\" {\n\t\t\tnewRefs = append(newRefs, or)\n\t\t}\n\t}\n\t_true := true\n\tnewRefs = append(newRefs, metav1.OwnerReference{\n\t\tAPIVersion:         \"apps.cloudrobotics.com/v1alpha1\",\n\t\tKind:               \"ResourceSet\",\n\t\tName:               set.Name,\n\t\tUID:                set.UID,\n\t\tBlockOwnerDeletion: &_true,\n\t})\n\tr.SetOwnerReferences(newRefs)\n}\n\n// canReplace determines whether an \"apply patch/update\" error is likely to be\n// resolved by deleting and recreating the resource. Some resources have\n// immutable fields (eg Job.spec.template) that can only be changed this way.\n// This is analogous to `kubectl apply --force`.\nfunc canReplace(resource *unstructured.Unstructured, patchErr error) bool {\n\tk := resource.GetKind()\n\te := patchErr.Error()\n\tif (k == \"DaemonSet\" || k == \"Deployment\" || k == \"Job\") && strings.Contains(e, \"field is immutable\") {\n\t\treturn true\n\t}\n\tif k == \"Service\" && (strings.Contains(e, \"field is immutable\") || strings.Contains(e, \"may not change once set\") || strings.Contains(e, \"can not be unset\")) {\n\t\treturn true\n\t}\n\tif k == \"PersistentVolume\" && strings.Contains(e, \"is immutable after creation\") {\n\t\tv, ok, err := unstructured.NestedString(resource.Object, \"spec\", \"persistentVolumeReclaimPolicy\")\n\t\tif err == nil && ok && v == \"Retain\" {\n\t\t\treturn true\n\t\t}\n\t\tslog.Info(\"Not replacing PersistentVolume since reclaim policy is not Retain\", slog.String(\"Policy\", v))\n\t}\n\tif (k == \"ValidatingWebhookConfiguration\" || k == \"MutatingWebhookConfiguration\") && strings.Contains(e, \"must be specified for an update\") {\n\t\treturn true\n\t}\n\n\t// TODO(rodrigoq): can other resources be safely replaced?\n\treturn false\n}\n\nfunc replace(ctx context.Context, client dynamic.ResourceInterface, resource *unstructured.Unstructured) (*unstructured.Unstructured, error) {\n\t// Foreground deletion means that the new job can't be created until the old\n\t// pods are gone, so updates to a currently-running job are safer.\n\tpolicy := metav1.DeletePropagationForeground\n\tdeleteOpts := metav1.DeleteOptions{PropagationPolicy: &policy}\n\tif err := client.Delete(ctx, resource.GetName(), deleteOpts); err != nil {\n\t\treturn nil, errors.Wrap(err, \"delete\")\n\t}\n\tres, err := client.Create(ctx, resource, metav1.CreateOptions{})\n\tif err != nil {\n\t\t// This is likely to occur if deletion is not immediate, in which case\n\t\t// this returns a transient AlreadyExists error, and the outer loop will\n\t\t// retry until the resource is deleted.\n\t\treturn nil, errors.Wrap(err, \"create\")\n\t}\n\treturn res, nil\n}\n\nfunc (s *Synk) applyOne(ctx context.Context, resource *unstructured.Unstructured, set *apps.ResourceSet) (apps.ResourceAction, error) {\n\t// If name is unset, we'd retrieve a list below and panic.\n\t// TODO: This may be valid if generateName is set instead. In this case we\n\t// want to create the resource in any case.\n\tif resource.GetName() == \"\" {\n\t\treturn apps.ResourceActionNone, errors.New(\"missing resource name\")\n\t}\n\tctx, span := trace.StartSpan(ctx, \"Apply \"+resource.GetName())\n\tdefer span.End()\n\t// GroupVersionKind is not sufficient to determine the REST API path to use\n\t// for the resource. We need to get this information from the RESTMapper,\n\t// which uses the discovery API to determine the right GroupVersionResource.\n\tgvk := resource.GroupVersionKind()\n\n\tmapping, err := s.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)\n\tif err != nil {\n\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"get REST mapping\")\n\t}\n\tvar client dynamic.ResourceInterface\n\tif mapping.Scope.Name() == meta.RESTScopeNameRoot {\n\t\tclient = s.client.Resource(mapping.Resource)\n\t} else {\n\t\tclient = s.client.Resource(mapping.Resource).Namespace(resource.GetNamespace())\n\t}\n\tresetAppliedAnnotation := false\n\tif err := setAppliedAnnotation(resource); err != nil {\n\t\tslog.Warn(\"Storing Applied Annotation failed\", ilog.Err(err))\n\t\tresetAppliedAnnotation = true\n\t}\n\n\t// Create the resource if it doesn't exist yet.\n\t_, getSpan := trace.StartSpan(ctx, \"Get \"+resource.GetName())\n\tcurrent, err := client.Get(ctx, resource.GetName(), metav1.GetOptions{})\n\tgetSpan.End()\n\tif k8serrors.IsNotFound(err) {\n\t\t_, createSpan := trace.StartSpan(ctx, \"Create \"+resource.GetName())\n\t\tres, err := client.Create(ctx, resource, metav1.CreateOptions{})\n\t\tcreateSpan.End()\n\t\tif err != nil {\n\t\t\treturn apps.ResourceActionCreate, errors.Wrap(err, \"create resource\")\n\t\t}\n\t\t*resource = *res\n\t\treturn apps.ResourceActionCreate, nil\n\t} else if err != nil {\n\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"get resource\")\n\t}\n\tif err := validateOwnerRefs(current, set); err != nil {\n\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"owner conflict\")\n\t}\n\n\t// Get what is running, what was installed and what we want to run.\n\tcurrentRaw, err := current.MarshalJSON()\n\tif err != nil {\n\t\treturn apps.ResourceActionNone, err\n\t}\n\tresourceRaw, err := resource.MarshalJSON()\n\tif err != nil {\n\t\treturn apps.ResourceActionNone, err\n\t}\n\tif resetAppliedAnnotation {\n\t\tdeleteAppliedAnnotation(current)\n\t}\n\toriginalRaw := getAppliedAnnotation(current)\n\n\tvar patchErr error\n\tif len(originalRaw) > 0 {\n\t\t// Try to patch it.\n\t\tvar (\n\t\t\tpatchType types.PatchType\n\t\t\tpatch     []byte\n\t\t)\n\t\tobj, err := scheme.Scheme.New(mapping.GroupVersionKind)\n\t\tif err == nil {\n\t\t\t// TODO: add option to dynamically load patch meta from discovery API\n\t\t\t// for full kubectl compatibility.\n\t\t\tpatchMeta, err := strategicpatch.NewPatchMetaFromStruct(obj)\n\t\t\tif err != nil {\n\t\t\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"lookup patch meta\")\n\t\t\t}\n\t\t\t// TODO: Make overwrite boolean configurable for full kubectl compatibility.\n\t\t\tpatch, err = strategicpatch.CreateThreeWayMergePatch(\n\t\t\t\toriginalRaw, resourceRaw, currentRaw,\n\t\t\t\tpatchMeta, true,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"create strategic-merge-patch\")\n\t\t\t}\n\t\t\tpatchType = types.StrategicMergePatchType\n\n\t\t} else if runtime.IsNotRegisteredError(err) {\n\t\t\tpatch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(\n\t\t\t\toriginalRaw, resourceRaw, currentRaw,\n\t\t\t\tmergepatch.RequireKeyUnchanged(\"apiVersion\"),\n\t\t\t\tmergepatch.RequireKeyUnchanged(\"kind\"),\n\t\t\t\tmergepatch.RequireMetadataKeyUnchanged(\"name\"),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"create json-merge-patch\")\n\t\t\t}\n\t\t\tpatchType = types.MergePatchType\n\t\t} else {\n\t\t\treturn apps.ResourceActionNone, errors.Wrap(err, \"instantiate object\")\n\t\t}\n\t\t// As the ownerReference is bumped, the patch will never be empty\n\t\t// and we cannot skip it.\n\n\t\t// CL https://github.com/kubernetes/kubernetes/pull/71156\n\t\t// added an option to fix a patch against a specific resourceVersion.\n\t\t// However, it isn't used anywhere in kubectl apply itself. Thus we don't do it here either.\n\t\t// Additionally the CL doesn't seem to implement valid behavior as the patch\n\t\t// retries will not update to a new resourceVersion and the failure would persist.\n\t\t_, patchSpan := trace.StartSpan(ctx, \"Patch \"+resource.GetName())\n\t\tres, err := client.Patch(ctx, resource.GetName(), patchType, patch, metav1.PatchOptions{})\n\t\tpatchSpan.End()\n\t\tif err == nil {\n\t\t\t// Successfully patched.\n\t\t\t*resource = *res\n\t\t\treturn apps.ResourceActionUpdate, nil\n\t\t}\n\t\tpatchErr = err\n\t} else {\n\t\t// We don't have lastApplied state, hence try a direct Update without a\n\t\t// 3-way-merge. This can happen if the resource is too large for the\n\t\t// annotation or has been deleted.\n\n\t\tresource.SetResourceVersion(current.GetResourceVersion())\n\n\t\t_, updateSpan := trace.StartSpan(ctx, \"Update \"+resource.GetName())\n\t\tres, err := client.Update(ctx, resource, metav1.UpdateOptions{})\n\t\tupdateSpan.End()\n\t\tif err == nil {\n\t\t\t// Successfully updated.\n\t\t\t*resource = *res\n\t\t\treturn apps.ResourceActionUpdate, nil\n\t\t}\n\t\tpatchErr = err\n\t}\n\n\t// If patching/updating failed, consider deleting and recreating the resource.\n\tif !canReplace(resource, patchErr) {\n\t\treturn apps.ResourceActionUpdate, errors.Wrap(patchErr, \"apply patch or update\")\n\t}\n\t_, replace_span := trace.StartSpan(ctx, \"Replace \"+resource.GetName())\n\tres, err := replace(ctx, client, resource)\n\treplace_span.End()\n\tif err != nil {\n\t\treturn apps.ResourceActionReplace, errors.Wrap(err, \"replace\")\n\t}\n\t*resource = *res\n\treturn apps.ResourceActionReplace, nil\n}\n\n// crdAvailable checks if all versions of the given CRD are present in the\n// server's discovery information. Callers must use s.Discovery.Invalidate()\n// to clear the discovery cache before calling this method to check against the\n// latest server state.\nfunc (s *Synk) crdAvailable(ucrd *unstructured.Unstructured) (bool, error) {\n\tvar crd apiextensions.CustomResourceDefinition\n\tif err := convert(ucrd, &crd); err != nil {\n\t\treturn false, err\n\t}\n\n\t// Get a list of versions to check for.\n\tvar versions []string\n\tfor _, v := range crd.Spec.Versions {\n\t\tif v.Served {\n\t\t\tversions = append(versions, v.Name)\n\t\t}\n\t}\n\n\tfor _, v := range versions {\n\t\tlist, err := s.discovery.ServerResourcesForGroupVersion(crd.Spec.Group + \"/\" + v)\n\t\tif err != nil {\n\t\t\t// We'd like to detect \"not found\" vs network errors here. But unfortunately\n\t\t\t// there's no canonical error being used.\n\t\t\tslog.Warn(\"ServerResourcesForGroupVersion failed\",\n\t\t\t\tslog.String(\"Group\", crd.Spec.Group),\n\t\t\t\tslog.String(\"Version\", v),\n\t\t\t\tilog.Err(err))\n\t\t\treturn false, nil\n\t\t}\n\t\tfound := false\n\t\tfor _, r := range list.APIResources {\n\t\t\tif r.Name == crd.Spec.Names.Plural {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\treturn true, nil\n}\n\nvar resourceSetGVR = schema.GroupVersionResource{\n\tGroup:    \"apps.cloudrobotics.com\",\n\tVersion:  \"v1alpha1\",\n\tResource: \"resourcesets\",\n}\n\nfunc (s *Synk) createResourceSet(ctx context.Context, rs *apps.ResourceSet) error {\n\trs.Kind = \"ResourceSet\"\n\trs.APIVersion = \"apps.cloudrobotics.com/v1alpha1\"\n\n\tvar u unstructured.Unstructured\n\tif err := convert(rs, &u); err != nil {\n\t\treturn err\n\t}\n\tres, err := s.client.Resource(resourceSetGVR).Create(ctx, &u, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn convert(res, rs)\n}\n\ntype applyResult struct {\n\tresource *unstructured.Unstructured\n\terr      error\n\taction   apps.ResourceAction\n}\n\nfunc (r *applyResult) String() string {\n\treturn fmt.Sprintf(\"%s action=%s error=%s\", resourceKey(r.resource), r.action, r.err)\n}\n\ntype applyResults map[string]*applyResult\n\nfunc (r applyResults) set(res *unstructured.Unstructured, action apps.ResourceAction, err error) {\n\tr[resourceKey(res)] = &applyResult{\n\t\tresource: res,\n\t\taction:   action,\n\t\terr:      err,\n\t}\n}\n\nfunc (r applyResults) failed(res *unstructured.Unstructured) bool {\n\tif x, ok := r[resourceKey(res)]; ok && x.err != nil {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (r applyResults) list() (l []*applyResult) {\n\tfor _, res := range r {\n\t\tl = append(l, res)\n\t}\n\tsort.Slice(l, func(i, j int) bool {\n\t\treturn lessUnstructured(l[i].resource, l[j].resource)\n\t})\n\treturn l\n}\n\nfunc (s *Synk) updateResourceSetStatus(ctx context.Context, rs *apps.ResourceSet, results applyResults) error {\n\ttype group map[schema.GroupVersionKind][]apps.ResourceStatus\n\tapplied, failed := group{}, group{}\n\n\tfor _, r := range results.list() {\n\t\tst := apps.ResourceStatus{\n\t\t\tNamespace:  r.resource.GetNamespace(),\n\t\t\tName:       r.resource.GetName(),\n\t\t\tAction:     r.action,\n\t\t\tUID:        string(r.resource.GetUID()),\n\t\t\tGeneration: r.resource.GetGeneration(),\n\t\t}\n\t\tif r.err != nil {\n\t\t\tst.Error = r.err.Error()\n\t\t}\n\t\tgvk := r.resource.GroupVersionKind()\n\t\tif r.err != nil {\n\t\t\tfailed[gvk] = append(failed[gvk], st)\n\t\t} else {\n\t\t\tapplied[gvk] = append(applied[gvk], st)\n\t\t}\n\t}\n\t// Attach group map as sorted status list.\n\tbuild := func(g group, list *[]apps.ResourceSetStatusGroup) {\n\t\tfor gvk, res := range g {\n\t\t\t*list = append(*list, apps.ResourceSetStatusGroup{\n\t\t\t\tGroup:   gvk.Group,\n\t\t\t\tVersion: gvk.Version,\n\t\t\t\tKind:    gvk.Kind,\n\t\t\t\tItems:   res,\n\t\t\t})\n\t\t}\n\t\tsort.Slice(*list, func(i, j int) bool {\n\t\t\treturn lessResourceSetStatusGroup(&(*list)[i], &(*list)[j])\n\t\t})\n\t}\n\tbuild(applied, &rs.Status.Applied)\n\tbuild(failed, &rs.Status.Failed)\n\n\trs.Status.FinishedAt = metav1.Now()\n\tif len(rs.Status.Failed) > 0 {\n\t\trs.Status.Phase = apps.ResourceSetPhaseFailed\n\t} else {\n\t\trs.Status.Phase = apps.ResourceSetPhaseSettled\n\t}\n\n\tvar u unstructured.Unstructured\n\tif err := convert(rs, &u); err != nil {\n\t\treturn err\n\t}\n\tres, err := s.client.Resource(resourceSetGVR).Update(ctx, &u, metav1.UpdateOptions{})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"update ResourceSet status\")\n\t}\n\treturn convert(res, rs)\n}\n\n// deleteFailedResourceSets deletes all failed ResourceSets of the given name\n// that have a lower version.\nfunc (s *Synk) deleteFailedResourceSets(ctx context.Context, name string, version int32) error {\n\tc := s.client.Resource(resourceSetGVR)\n\n\tlist, err := c.List(ctx, metav1.ListOptions{\n\t\tLabelSelector: \"name=\" + name,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"list existing resources\")\n\t}\n\tfor _, r := range list.Items {\n\t\tphase, found, err := unstructured.NestedString(r.Object, \"status\", \"phase\")\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"failed to get status.phase from ResourceSet %q\", r.GetName())\n\t\t}\n\t\tif !found || phase != \"Failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tn, v, ok := decodeResourceSetName(r.GetName())\n\t\tif !ok || n != name || v >= version {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: should we possibly opt for foreground deletion here so\n\t\t// we only return after all dependents have been deleted as well?\n\t\t// kubectl doesn't allow to opt into foreground deletion in general but\n\t\t// here it would likely bring us closer to the apply --prune semantics.\n\t\tif err := c.Delete(ctx, r.GetName(), metav1.DeleteOptions{}); err != nil {\n\t\t\treturn errors.Wrapf(err, \"delete ResourceSet %q\", r.GetName())\n\t\t}\n\t}\n\treturn nil\n}\n\n// deleteResourceSets deletes all ResourceSets of the given name that have a\n// lower version.\nfunc (s *Synk) deleteResourceSets(ctx context.Context, name string, version int32) error {\n\tc := s.client.Resource(resourceSetGVR)\n\n\tlist, err := c.List(ctx, metav1.ListOptions{\n\t\tLabelSelector: \"name=\" + name,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"list existing resources\")\n\t}\n\tfor _, r := range list.Items {\n\t\tn, v, ok := decodeResourceSetName(r.GetName())\n\t\tif !ok || n != name || v >= version {\n\t\t\tcontinue\n\t\t}\n\t\t// TODO: should we possibly opt for foreground deletion here so\n\t\t// we only return after all dependents have been deleted as well?\n\t\t// kubectl doesn't allow to opt into foreground deletion in general but\n\t\t// here it would likely bring us closer to the apply --prune semantics.\n\t\tif err := c.Delete(ctx, r.GetName(), metav1.DeleteOptions{}); err != nil {\n\t\t\treturn errors.Wrapf(err, \"delete ResourceSet %q\", r.GetName())\n\t\t}\n\t}\n\treturn nil\n}\n\n// next returns the next version for the resources name.\nfunc (s *Synk) next(ctx context.Context, name string) (version int32, err error) {\n\tlist, err := s.client.Resource(resourceSetGVR).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn 0, errors.Wrap(err, \"list existing ResourceSets\")\n\t}\n\tvar curVersion int32\n\tfor _, r := range list.Items {\n\t\tn, v, ok := decodeResourceSetName(r.GetName())\n\t\tif !ok || n != name {\n\t\t\tcontinue\n\t\t}\n\t\tif v > curVersion {\n\t\t\tcurVersion = v\n\t\t}\n\t}\n\treturn curVersion + 1, nil\n}\n\n// Filter for helm-hooks that mark test resources. See\n// https://github.com/googlecloudrobotics/core/issues/20\nfunc isTestResource(r *unstructured.Unstructured) bool {\n\thook, ok := r.GetAnnotations()[\"helm.sh/hook\"]\n\treturn ok && strings.HasPrefix(hook, \"test-\")\n}\n\nfunc isCustomResourceDefinition(r *unstructured.Unstructured) bool {\n\treturn strings.HasPrefix(r.GetAPIVersion(), \"apiextensions.k8s.io/\") && r.GetKind() == \"CustomResourceDefinition\"\n}\n\nfunc separateCRDsFromResources(resources []*unstructured.Unstructured) (crds []*unstructured.Unstructured, regulars []*unstructured.Unstructured) {\n\tfor _, r := range resources {\n\t\tif isCustomResourceDefinition(r) {\n\t\t\tcrds = append(crds, r)\n\t\t} else {\n\t\t\tregulars = append(regulars, r)\n\t\t}\n\t}\n\treturn crds, regulars\n}\n\nfunc filter(in []*unstructured.Unstructured, f func(*unstructured.Unstructured) bool) (out []*unstructured.Unstructured) {\n\tfor _, r := range in {\n\t\tif f(r) {\n\t\t\tout = append(out, r)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc resourceSetName(s string, v int32) string {\n\treturn fmt.Sprintf(\"%s.v%d\", s, v)\n}\n\nvar namePat = regexp.MustCompile(`^(.+)\\.v([0-9]+)$`)\n\nfunc decodeResourceSetName(s string) (string, int32, bool) {\n\tres := namePat.FindStringSubmatch(s)\n\tif len(res) == 0 {\n\t\treturn \"\", 0, false\n\t}\n\tversion, err := strconv.Atoi(res[2])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn res[1], int32(version), true\n}\n\nfunc sortResources(res []*unstructured.Unstructured) {\n\tsort.Slice(res, func(i, j int) (b bool) {\n\t\treturn lessUnstructured(res[i], res[j])\n\t})\n}\n\nfunc resourceKey(r *unstructured.Unstructured) string {\n\tgvk := r.GroupVersionKind()\n\treturn fmt.Sprintf(\"%s/%s/%s\",\n\t\tgvkKey(gvk.Group, gvk.Version, gvk.Kind),\n\t\tr.GetNamespace(),\n\t\tr.GetName())\n}\n\nfunc gvkKey(group, version, kind string) string {\n\treturn fmt.Sprintf(\"%s/%s/%s\", group, version, kind)\n}\n\n// convert a resource from one type representation to another one.\nfunc convert(from, to runtime.Object) error {\n\tb, err := json.Marshal(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(b, &to)\n}\n"
  },
  {
    "path": "src/go/pkg/synk/synk_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage synk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/pkg/errors\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta/testrestmapper\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/discovery\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tk8stest \"k8s.io/client-go/testing\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// The fake discovery client has quite a bit of code but still returns empty results\n// even when pointed at the Fake object of the dynamic client.\n// Thus we implement our own static one.\ntype fakeCachedDiscoveryClient struct {\n\tdiscovery.CachedDiscoveryInterface\n}\n\nfunc (d *fakeCachedDiscoveryClient) Invalidate() {}\n\nfunc (d *fakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {\n\treturn nil, []*metav1.APIResourceList{\n\t\t{\n\t\t\tGroupVersion: \"v1\",\n\t\t\tAPIResources: []metav1.APIResource{\n\t\t\t\t{Kind: \"Pod\", Namespaced: true},\n\t\t\t\t{Kind: \"Namespace\", Namespaced: false},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\ntype fixture struct {\n\t*testing.T\n\tfake *k8stest.Fake\n\n\t// Starting state the respective client will report.\n\tobjects []runtime.Object\n\t// Actions we want to see called against the respective client.\n\tactions []k8stest.Action\n}\n\nfunc newFixture(t *testing.T) *fixture {\n\treturn &fixture{T: t}\n}\n\nfunc (f *fixture) newSynk() *Synk {\n\tsc := runtime.NewScheme()\n\tscheme.AddToScheme(sc)\n\tapps.AddToScheme(sc) // For tests with CRDs.\n\tvar (\n\t\tclient = dynamicfake.NewSimpleDynamicClient(sc, f.objects...)\n\t\ts      = New(client, &fakeCachedDiscoveryClient{})\n\t)\n\ts.mapper = testrestmapper.TestOnlyStaticRESTMapper(sc)\n\ts.resetMapper = func() {}\n\tf.fake = &client.Fake\n\treturn s\n}\n\nfunc (f *fixture) addObjects(objs ...runtime.Object) {\n\tf.objects = append(f.objects, objs...)\n}\n\nfunc (f *fixture) expectActions(as ...k8stest.Action) {\n\tf.actions = append(f.actions, as...)\n}\n\nfunc (f *fixture) verifyWriteActions() {\n\twrites := filterReadActions(f.fake.Actions())\n\n\tif !reflect.DeepEqual(writes, f.actions) {\n\t\tf.Errorf(\"writes did not match\")\n\t\tf.Logf(\"received:\")\n\t\tfor i, a := range writes {\n\t\t\tf.Logf(\"%d: %s\", i, sprintAction(a))\n\t\t}\n\t\tf.Logf(\"expected:\")\n\t\tfor i, a := range f.actions {\n\t\t\tf.Logf(\"%d: %s\", i, sprintAction(a))\n\t\t}\n\t}\n}\n\nfunc TestSynk_IsTransientErr(t *testing.T) {\n\ttests := []struct {\n\t\terr  error\n\t\twant bool\n\t}{\n\t\t{\n\t\t\terrors.New(\"generic error\"),\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\ttransientErr{errors.New(\"transientErr struct\")},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t&transientErr{errors.New(\"transientErr pointer\")},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\tk8serrors.NewUnauthorized(\"unauthorized\"),\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\tk8serrors.NewResourceExpired(\"gone\"),\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\tk8serrors.NewForbidden(schema.GroupResource{\n\t\t\t\tGroup:    \"apps\",\n\t\t\t\tResource: \"deployments\",\n\t\t\t}, \"my-deployment\", errors.New(\"unable to create new content in namespace app-test-chart because it is being terminated\")),\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.err.Error(), func(t *testing.T) {\n\t\t\tif got := IsTransientErr(tc.err); got != tc.want {\n\t\t\t\tt.Errorf(\"IsTransientErr(%v)=%v, want %v\", tc.err, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TODO(rodrigoq): test Apply() directly rather than the private methods\nfunc TestSynk_initialize(t *testing.T) {\n\tctx := context.Background()\n\ts := newFixture(t).newSynk()\n\n\t_, _, err := s.initialize(ctx, &ApplyOptions{name: \"test\"},\n\t\tnewUnstructured(\"v1\", \"Pod\", \"ns2\", \"pod1\"),\n\t\tnewUnstructured(\"apps/v1\", \"Deployment\", \"ns1\", \"deploy1\"),\n\t\tnewUnstructured(\"v1\", \"Pod\", \"ns1\", \"pod1\"),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot, err := s.client.Resource(resourceSetGVR).Get(ctx, \"test.v1\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar want unstructured.Unstructured\n\tunmarshalYAML(t, &want, `\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: ResourceSet\nmetadata:\n  labels:\n    name: test\n  name: test.v1\nspec:\n  resources:\n  - version: v1\n    kind: Pod\n    items:\n    - name: pod1\n      namespace: ns1\n    - name: pod1\n      namespace: ns2\n  - group: apps\n    version: v1\n    kind: Deployment\n    items:\n    - namespace: ns1\n      name: deploy1\nstatus:\n  phase: Pending\n`)\n\tif want.GetName() != got.GetName() {\n\t\tt.Errorf(\"expected name %q but got %q\", want.GetName(), got.GetName())\n\t}\n\twantPhase, _, _ := unstructured.NestedString(want.Object, \"status\", \"phase\")\n\tgotPhase, _, _ := unstructured.NestedString(got.Object, \"status\", \"phase\")\n\tif wantPhase != gotPhase {\n\t\tt.Errorf(\"expected status phase %q but got %q\", wantPhase, gotPhase)\n\t}\n\tif !reflect.DeepEqual(want.Object[\"spec\"], got.Object[\"spec\"]) {\n\t\tt.Errorf(\"expected spec\\n%v\\nbut got\\n%v\", want.Object[\"spec\"], got.Object[\"spec\"])\n\t}\n}\n\nfunc TestSynk_updateResourceSetStatus(t *testing.T) {\n\tctx := context.Background()\n\tf := newFixture(t)\n\ts := f.newSynk()\n\n\trs := &apps.ResourceSet{\n\t\tObjectMeta: metav1.ObjectMeta{Name: \"set1\"},\n\t}\n\tif err := s.createResourceSet(ctx, rs); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresults := applyResults{\n\t\t\"/v1/Pod/ns1/pod1\": &applyResult{\n\t\t\tresource: newUnstructured(\"v1\", \"Pod\", \"ns1\", \"pod1\"),\n\t\t\taction:   apps.ResourceActionCreate,\n\t\t\terr:      errors.New(\"oops\"),\n\t\t},\n\t\t\"/v1/Pod/ns1/pod2\": &applyResult{\n\t\t\tresource: newUnstructured(\"v1\", \"Pod\", \"ns1\", \"pod2\"),\n\t\t\taction:   apps.ResourceActionCreate,\n\t\t},\n\t\t\"/v1/Pod/ns2/pod1\": &applyResult{\n\t\t\tresource: newUnstructured(\"v1\", \"Pod\", \"ns2\", \"pod1\"),\n\t\t\taction:   apps.ResourceActionUpdate,\n\t\t},\n\t\t\"apps/v1/Deployment/ns1/deploy1\": &applyResult{\n\t\t\tresource: newUnstructured(\"apps/v1\", \"Deployment\", \"ns1\", \"deploy1\"),\n\t\t\taction:   apps.ResourceActionCreate,\n\t\t},\n\t}\n\terr := s.updateResourceSetStatus(ctx, rs, results)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot, err := s.client.Resource(resourceSetGVR).Get(ctx, \"set1\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar want unstructured.Unstructured\n\tunmarshalYAML(t, &want, `\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: ResourceSet\nmetadata:\n  name: set1\nstatus:\n  phase: Failed\n  applied:\n  failed:\n  - version: v1\n    kind: Pod\n    items:\n    - name: pod1\n      namespace: ns1\n      error: oops\n      action: Create\n  applied:\n  - version: v1\n    kind: Pod\n    items:\n    - name: pod2\n      namespace: ns1\n      action: Create\n    - name: pod1\n      namespace: ns2\n      action: Update\n  - group: apps\n    version: v1\n    kind: Deployment\n    items:\n    - namespace: ns1\n      name: deploy1\n      action: Create\n`)\n\tif v, _, _ := unstructured.NestedString(got.Object, \"status\", \"finishedAt\"); v == \"\" {\n\t\tt.Errorf(\"finishedAt timestamp was not set\")\n\t}\n\t// Remove unknown timestamps before running DeepEqual.\n\tunstructured.RemoveNestedField(got.Object, \"status\", \"startedAt\")\n\tunstructured.RemoveNestedField(got.Object, \"status\", \"finishedAt\")\n\n\tif !reflect.DeepEqual(got.Object[\"status\"], want.Object[\"status\"]) {\n\t\tt.Errorf(\"expected status:\\n%q\\nbut got:\\n%q\", want.Object[\"status\"], got.Object[\"status\"])\n\t}\n}\n\n// Hardcode some GVR mappings for easy use in tests. The only other way is\n// setting up a full RestMapper.\nvar gvrs = map[string]schema.GroupVersionResource{\n\t\"configmaps\":  {Version: \"v1\", Resource: \"configmaps\"},\n\t\"deployments\": {Group: \"apps\", Version: \"v1\", Resource: \"deployments\"},\n\t\"approllouts\": {Group: \"apps.cloudrobotics.com\", Version: \"v1alpha1\", Resource: \"approllouts\"},\n}\n\nfunc TestSynk_applyAllIsUpdatingResources(t *testing.T) {\n\t// We have to use properly typed objects for strategic-merge-patch targets\n\t// as the patch operation will fail otherwise.\n\tvar cmBefore, cmUpdate corev1.ConfigMap\n\tunmarshalYAML(t, &cmBefore, `\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  namespace: foo1\n  name: cm1\ndata:\n  foo1: bar1\n  foo2: bar2`)\n\tf := newFixture(t)\n\t// cm1 already exists beforehand, so we expect an update.\n\tf.addObjects(&cmBefore)\n\n\tunmarshalYAML(t, &cmUpdate, `\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  namespace: foo1\n  name: cm1\ndata:\n  foo2: baz2\n  foo3: bar3`)\n\tcm := toUnstructured(t, &cmUpdate)\n\n\tset := &apps.ResourceSet{}\n\tset.Name = \"test.v1\"\n\tset.UID = \"deadbeef\"\n\n\t// Note: We can't test applying an Unstructured object here, as the\n\t// fake client doesn't support strategic merge patches:\n\t// https://github.com/kubernetes/client-go/issues/613\n\tresults, err := f.newSynk().applyAll(context.Background(), set, &ApplyOptions{name: \"test\"},\n\t\tcm.DeepCopy(),\n\t)\n\tif err != nil {\n\t\tt.Error(err)\n\t\tfor _, res := range results {\n\t\t\tt.Log(res)\n\t\t}\n\t\treturn\n\t}\n\n\t_true := true\n\townerRef := metav1.OwnerReference{\n\t\tAPIVersion:         \"apps.cloudrobotics.com/v1alpha1\",\n\t\tKind:               \"ResourceSet\",\n\t\tName:               set.Name,\n\t\tUID:                set.UID,\n\t\tBlockOwnerDeletion: &_true,\n\t}\n\tcm.SetOwnerReferences([]metav1.OwnerReference{ownerRef})\n\n\tsetAppliedAnnotation(cm)\n\n\tf.expectActions(\n\t\tk8stest.NewUpdateAction(gvrs[\"configmaps\"], \"foo1\", cm),\n\t)\n\tf.verifyWriteActions()\n}\n\nfunc TestSynk_applyAllIsCreatingResources(t *testing.T) {\n\tf := newFixture(t)\n\n\t// Support for a standard merge-patch was only added to the client-go testing\n\t// mock as of kubernetes-1.14.0, which we cannot upgrade to yet.\n\t// Thus we cannot valid the standard merge patch type for CRDs yet.\n\trollout := newUnstructured(\"apps.cloudrobotics.com/v1alpha1\", \"AppRollout\", \"foo1\", \"rollout1\")\n\tdeploy := newUnstructured(\"apps/v1\", \"Deployment\", \"foo2\", \"dp1\")\n\n\tset := &apps.ResourceSet{}\n\tset.Name = \"test.v1\"\n\tset.UID = \"deadbeef\"\n\n\tresults, err := f.newSynk().applyAll(context.Background(), set, &ApplyOptions{name: \"test\"},\n\t\trollout.DeepCopy(),\n\t\tdeploy.DeepCopy(),\n\t)\n\tif err != nil {\n\t\tt.Error(err)\n\t\tfor _, res := range results {\n\t\t\tt.Log(res)\n\t\t}\n\t\treturn\n\t}\n\n\t_true := true\n\townerRef := metav1.OwnerReference{\n\t\tAPIVersion:         \"apps.cloudrobotics.com/v1alpha1\",\n\t\tKind:               \"ResourceSet\",\n\t\tName:               set.Name,\n\t\tUID:                set.UID,\n\t\tBlockOwnerDeletion: &_true,\n\t}\n\trollout.SetOwnerReferences([]metav1.OwnerReference{ownerRef})\n\tdeploy.SetOwnerReferences([]metav1.OwnerReference{ownerRef})\n\n\tsetAppliedAnnotation(deploy)\n\tsetAppliedAnnotation(rollout)\n\n\tf.expectActions(\n\t\tk8stest.NewCreateAction(gvrs[\"approllouts\"], \"foo1\", rollout),\n\t\tk8stest.NewCreateAction(gvrs[\"deployments\"], \"foo2\", deploy),\n\t)\n\tf.verifyWriteActions()\n}\n\nfunc TestSynk_applyAllRetriesResourceExpired(t *testing.T) {\n\t// deploy is the input to applyAll(), annotatedDeploy is the expected output.\n\t// TODO(rodrigoq): change verifyWriteActions() to avoid this boilerplate\n\tdeploy := newUnstructured(\"apps/v1\", \"Deployment\", \"foo1\", \"dp1\")\n\tannotatedDeploy := deploy.DeepCopy()\n\tset := &apps.ResourceSet{}\n\tset.Name = \"test.v1\"\n\tset.UID = \"deadbeef\"\n\t_true := true\n\townerRef := metav1.OwnerReference{\n\t\tAPIVersion:         \"apps.cloudrobotics.com/v1alpha1\",\n\t\tKind:               \"ResourceSet\",\n\t\tName:               set.Name,\n\t\tUID:                set.UID,\n\t\tBlockOwnerDeletion: &_true,\n\t}\n\tannotatedDeploy.SetOwnerReferences([]metav1.OwnerReference{ownerRef})\n\tsetAppliedAnnotation(annotatedDeploy)\n\temptyPatch := []byte(`{}`)\n\n\ttests := []struct {\n\t\tdesc    string\n\t\tverb    string\n\t\tobjects []runtime.Object\n\t\tactions []k8stest.Action\n\t}{{\n\t\tdesc:    \"create deployment returns ResourceExpired\",\n\t\tverb:    \"create\",\n\t\tobjects: []runtime.Object{},\n\t\tactions: []k8stest.Action{\n\t\t\tk8stest.NewCreateAction(gvrs[\"deployments\"], \"foo1\", annotatedDeploy),\n\t\t\tk8stest.NewCreateAction(gvrs[\"deployments\"], \"foo1\", annotatedDeploy),\n\t\t},\n\t}, {\n\t\tdesc:    \"patch deployment returns ResourceExpired\",\n\t\tverb:    \"patch\",\n\t\tobjects: []runtime.Object{annotatedDeploy},\n\t\tactions: []k8stest.Action{\n\t\t\tk8stest.NewPatchAction(gvrs[\"deployments\"], \"foo1\", \"dp1\", types.StrategicMergePatchType, emptyPatch),\n\t\t\tk8stest.NewPatchAction(gvrs[\"deployments\"], \"foo1\", \"dp1\", types.StrategicMergePatchType, emptyPatch),\n\t\t},\n\t}, {\n\t\tdesc:    \"update deployment returns ResourceExpired\",\n\t\tverb:    \"update\",\n\t\tobjects: []runtime.Object{deploy},\n\t\tactions: []k8stest.Action{\n\t\t\tk8stest.NewUpdateAction(gvrs[\"deployments\"], \"foo1\", annotatedDeploy),\n\t\t\tk8stest.NewUpdateAction(gvrs[\"deployments\"], \"foo1\", annotatedDeploy),\n\t\t},\n\t}}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tf := newFixture(t)\n\t\t\tf.addObjects(tc.objects...)\n\t\t\ts := f.newSynk()\n\t\t\t// ResourceExpired should be treated as a transient error.\n\t\t\tf.fake.PrependReactor(tc.verb, \"deployments\", func(action k8stest.Action) (bool, runtime.Object, error) {\n\t\t\t\treturn true, nil, k8serrors.NewResourceExpired(\"gone\")\n\t\t\t})\n\n\t\t\t_, err := s.applyAll(context.Background(), set, &ApplyOptions{name: \"test\"},\n\t\t\t\tdeploy.DeepCopy(),\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"applyAll() succeeded unexpectedly, want ResourceExpired\")\n\t\t\t}\n\n\t\t\t// applyAll() should retry once before failing.\n\t\t\tf.expectActions(tc.actions...)\n\t\t\tf.verifyWriteActions()\n\t\t})\n\t}\n}\n\nfunc TestSynk_skipsTestResources(t *testing.T) {\n\tctx := context.Background()\n\ts := newFixture(t).newSynk()\n\n\ttestPod := newUnstructured(\"v1\", \"Pod\", \"ns\", \"pod2\")\n\ttestPod.SetAnnotations(map[string]string{\"helm.sh/hook\": \"test-success\"})\n\n\t_, _, err := s.initialize(context.Background(), &ApplyOptions{name: \"test\"},\n\t\tnewUnstructured(\"v1\", \"Pod\", \"ns\", \"pod1\"),\n\t\ttestPod,\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot, err := s.client.Resource(resourceSetGVR).Get(ctx, \"test.v1\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar want unstructured.Unstructured\n\tunmarshalYAML(t, &want, `\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: ResourceSet\nmetadata:\n  labels:\n    name: test\n  name: test.v1\nspec:\n  resources:\n  - version: v1\n    kind: Pod\n    items:\n    - name: pod1\n      namespace: ns\nstatus:\n  phase: Pending\n`)\n\tif !reflect.DeepEqual(want.Object[\"spec\"], got.Object[\"spec\"]) {\n\t\tt.Errorf(\"expected spec\\n%v\\nbut got\\n%v\", want.Object[\"spec\"], got.Object[\"spec\"])\n\t}\n}\n\nfunc TestSynk_deleteResourceSets(t *testing.T) {\n\tctx := context.Background()\n\tnu := func(name, version string) *unstructured.Unstructured {\n\t\tu := newUnstructured(\"apps.cloudrobotics.com/v1alpha1\", \"ResourceSet\", \"\", name+\".\"+version)\n\t\tu.SetLabels(map[string]string{\"name\": name})\n\t\treturn u\n\t}\n\tf := newFixture(t)\n\tf.addObjects(\n\t\tnu(\"test\", \"v2\"),\n\t\tnu(\"bad_name\", \"\"),\n\t\tnu(\"other\", \"v3\"),\n\t\tnu(\"test\", \"v4\"),\n\t\tnu(\"test\", \"v7\"),\n\t\tnu(\"test\", \"v8\"),\n\t)\n\tsynk := f.newSynk()\n\n\terr := synk.deleteResourceSets(ctx, \"test\", 7)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tf.expectActions(\n\t\tk8stest.NewRootDeleteAction(resourceSetGVR, \"test.v2\"),\n\t\tk8stest.NewRootDeleteAction(resourceSetGVR, \"test.v4\"),\n\t)\n\tf.verifyWriteActions()\n}\n\nfunc TestSynk_deleteFailedResourceSets(t *testing.T) {\n\tctx := context.Background()\n\tnu := func(name, version string, failed bool) *unstructured.Unstructured {\n\t\tu := newUnstructured(\"apps.cloudrobotics.com/v1alpha1\", \"ResourceSet\", \"\", name+\".\"+version)\n\t\tu.SetLabels(map[string]string{\"name\": name})\n\t\tif failed {\n\t\t\tunstructured.SetNestedField(u.Object, \"Failed\", \"status\", \"phase\")\n\t\t}\n\t\treturn u\n\t}\n\tf := newFixture(t)\n\tf.addObjects(\n\t\tnu(\"test\", \"v2\", true),\n\t\tnu(\"test\", \"v4\", false),\n\t\tnu(\"test\", \"v6\", true),\n\t\tnu(\"test\", \"v7\", true),\n\t\tnu(\"test\", \"v8\", true),\n\t)\n\tsynk := f.newSynk()\n\n\terr := synk.deleteFailedResourceSets(ctx, \"test\", 7)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tf.expectActions(\n\t\tk8stest.NewRootDeleteAction(resourceSetGVR, \"test.v2\"),\n\t\tk8stest.NewRootDeleteAction(resourceSetGVR, \"test.v6\"),\n\t)\n\tf.verifyWriteActions()\n}\n\nfunc TestSynk_populateNamespaces(t *testing.T) {\n\tf := newFixture(t)\n\ts := f.newSynk()\n\n\tvar (\n\t\tns1  = newUnstructured(\"v1\", \"Namespace\", \"\", \"ns1\")\n\t\tpod1 = newUnstructured(\"v1\", \"Pod\", \"ns1\", \"pod1\")\n\t\tpod2 = newUnstructured(\"v1\", \"Pod\", \"\", \"pod1\")\n\t\tcr1  = newUnstructured(\"example.org/v1\", \"Example\", \"\", \"pod1\")\n\t)\n\tvar exampleCRD unstructured.Unstructured\n\tunmarshalYAML(t, &exampleCRD, `\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefition\nmetadata:\n  name: examples.example.org\nspec:\n  group: example.org\n  names:\n    kind: Example\n  scope: Namespaced\n  versions:\n    - name: v1\n      served: true\n      storage: true`)\n\n\tif err := s.populateNamespaces(\n\t\tcontext.Background(),\n\t\t\"ns2\",\n\t\t[]*unstructured.Unstructured{&exampleCRD},\n\t\tns1, pod1, pod2, cr1,\n\t); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ns1.GetNamespace() != \"\" {\n\t\tt.Errorf(\"unexpected namespace %q added to ns1\", ns1.GetNamespace())\n\t}\n\tif pod1.GetNamespace() != \"ns1\" {\n\t\tt.Errorf(\"unexpected namespace change to %q to pod1\", pod1.GetNamespace())\n\t}\n\tif pod2.GetNamespace() != \"ns2\" {\n\t\tt.Errorf(\"unexpected namesapce %q on pod2\", pod2.GetNamespace())\n\t}\n\tif cr1.GetNamespace() != \"ns2\" {\n\t\tt.Errorf(\"unexpected namesapce %q on cr1\", cr1.GetNamespace())\n\t}\n}\n\nfunc TestSynk_skipLastAppliedAnnotationForLargeResource(t *testing.T) {\n\tyaml := `\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  namespace: foo1\n  name: cm1\ndata:\n  foo1: ` + strings.Repeat(\"x\", totalAnnotationSizeLimitB)\n\n\tvar cmLarge unstructured.Unstructured\n\tunmarshalYAML(t, &cmLarge, yaml)\n\tif err := setAppliedAnnotation(&cmLarge); err == nil {\n\t\tt.Errorf(\"expected error setting large annotation, got nil\")\n\t}\n\n\tif applied := getAppliedAnnotation(&cmLarge); len(applied) > 0 {\n\t\tt.Errorf(\"expected no last-applied annotation set, but got %v\", applied)\n\t}\n}\n\nfunc newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {\n\tvar u unstructured.Unstructured\n\tu.SetAPIVersion(apiVersion)\n\tu.SetKind(kind)\n\tu.SetNamespace(namespace)\n\tu.SetName(name)\n\treturn &u\n}\n\nfunc unmarshalYAML(t *testing.T, v interface{}, s string) {\n\tt.Helper()\n\tif err := yaml.Unmarshal([]byte(strings.TrimSpace(s)), v); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc toUnstructured(t *testing.T, o runtime.Object) *unstructured.Unstructured {\n\tvar u unstructured.Unstructured\n\tif err := convert(o, &u); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn &u\n}\n\n// filterReadActions drops read-only actions that we don't care about to verify\n// the correct behavior.\nfunc filterReadActions(actions []k8stest.Action) (ret []k8stest.Action) {\n\tfor _, a := range actions {\n\t\tif v := a.GetVerb(); v == \"watch\" || v == \"list\" || v == \"get\" {\n\t\t\tcontinue\n\t\t}\n\t\tret = append(ret, a)\n\t}\n\treturn ret\n}\n\nfunc sprintAction(a k8stest.Action) string {\n\tswitch v := a.(type) {\n\tcase k8stest.DeleteActionImpl:\n\t\treturn fmt.Sprintf(\"DELETE %s/%s %s/%s\", v.Resource, v.Subresource, v.Namespace, v.Name)\n\tcase k8stest.CreateActionImpl:\n\t\treturn fmt.Sprintf(\"CREATE %s/%s %s/%s: %v\", v.Resource, v.Subresource, v.Namespace, v.Name, v.Object.(*unstructured.Unstructured))\n\tcase k8stest.UpdateActionImpl:\n\t\treturn fmt.Sprintf(\"UPDATE %s/%s %s: %v\", v.Resource, v.Subresource, v.Namespace, v.Object.(*unstructured.Unstructured))\n\tcase k8stest.PatchActionImpl:\n\t\treturn fmt.Sprintf(\"PATCH %s/%s %s/%s: %s %s\", v.Resource, v.Subresource, v.Namespace, v.Name, v.PatchType, v.Patch)\n\tdefault:\n\t\treturn fmt.Sprintf(\"<UNKNOWN ACTION %T>\", a)\n\t}\n}\n"
  },
  {
    "path": "src/go/tests/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\", \"go_test\")\nload(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"large\",\n    timeout = \"long\",\n    srcs = [\"k8s_integration_test.go\"],\n    embed = [\":go_default_library\"],\n    tags = [\"external\"],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/kubeutils:go_default_library\",\n        \"@com_github_googlecloudrobotics_ilog//:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/scheme:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library\",\n    ],\n)\n\nsh_test(\n    name = \"relay_test\",\n    srcs = [\"relay_test.sh\"],\n    data = [\n        \"@kubernetes_helm//:helm\",\n    ],\n    tags = [\"external\"],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"k8s_integration_test_auth_helper.go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/go/tests\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@io_k8s_apimachinery//pkg/util/net:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/yaml:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//util/jsonpath:go_default_library\",\n        \"@io_k8s_klog_v2//:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n        \"@org_golang_x_oauth2//google:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/tests/apps/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_test\")\n\ngo_test(\n    name = \"go_default_test\",\n    size = \"large\",\n    timeout = \"long\",\n    srcs = [\"apps_test.go\"],\n    data = [\n        \"//src/app_charts/base:base-test\",\n        \"//src/go/cmd/synk\",\n        \"@kubernetes_helm//:helm\",\n    ],\n    rundir = \".\",\n    tags = [\n        \"manual\",\n        \"requires-access-token\",\n        \"requires-docker\",\n    ],\n    deps = [\n        \"//src/go/pkg/apis/apps/v1alpha1:go_default_library\",\n        \"//src/go/pkg/kubetest:go_default_library\",\n        \"@com_github_cenkalti_backoff//:go_default_library\",\n        \"@io_k8s_api//apps/v1:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/api/errors:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/tests/apps/apps_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage apps\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff\"\n\tcrcapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubetest\"\n\tapps \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\nconst (\n\trobotClusterName = \"robot\"\n\n\tinlineChartTemplate = `\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: ChartAssignment\nmetadata:\n  name: {{ .name }}\n  namespace: default\nspec:\n  clusterName: {{ .cluster }}\n  namespaceName: {{ .namespace }}\n  chart:\n    inline: \"{{ .chart }}\"`\n\tgoodDeployment = `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: test\n  template:\n    metadata:\n      labels:\n        app: test\n    spec:\n      containers:\n      - name: test\n        image: \"gcr.io/google-containers/busybox:latest\"\n        args: [\"sleep\", \"999999999\"]`\n\tdeploymentWithBadLabels = `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: test\n  template:\n    metadata:\n      labels:\n        app: this-label-does-not-match\n    spec:\n      containers:\n      - name: test\n        image: \"gcr.io/google-containers/busybox:latest\"\n        args: [\"sleep\", \"999999999\"]`\n\tbadJob = `\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: test\nspec:\n  template:\n    metadata:\n      labels:\n        app: test\n    spec:\n      restartPolicy: Never\n      containers:\n      - name: test\n        image: \"gcr.io/google-containers/busybox:latest\"\n        args: [\"false\"]`\n\tgoodJob = `\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: test\nspec:\n  template:\n    metadata:\n      labels:\n        app: test\n    spec:\n      restartPolicy: Never\n      containers:\n      - name: test\n        image: \"gcr.io/google-containers/busybox:latest\"\n        args: [\"true\"]`\n)\n\nfunc TestAll(t *testing.T) {\n\tenv := kubetest.New(t, kubetest.Config{\n\t\tClusters: []kubetest.ClusterConfig{\n\t\t\t{Name: robotClusterName},\n\t\t},\n\t\tSchemeFunc: crcapps.AddToScheme,\n\t})\n\tdefer env.Teardown()\n\n\tenv.InstallChartArchive(\n\t\trobotClusterName, \"base-test\", \"default\",\n\t\t\"src/app_charts/base/base-test-0.0.1.tgz\",\n\t\tmap[string]string{\n\t\t\t\"project\":         \"\",\n\t\t\t\"robot.name\":      robotClusterName,\n\t\t\t\"registry\":        os.Getenv(\"REGISTRY\"),\n\t\t\t\"webhook.enabled\": \"false\",\n\t\t},\n\t)\n\tif err := backoff.Retry(\n\t\tfunc() error {\n\t\t\treturn kubetest.DeploymentReady(env.Ctx(), env.Client(robotClusterName), \"default\", \"chart-assignment-controller\")\n\t\t},\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(3*time.Second), 40),\n\t); err != nil {\n\t\tt.Errorf(\"wait for chart-assignment-controller: %s\", err)\n\t\tt.Fatalf(\"maybe REGISTRY or ACCESS_TOKEN is not set?\")\n\t}\n\n\tenv.Run(\n\t\ttestCreateChartAssignment_WithChartReference_Works,\n\t\ttestCreateChartAssignment_WithInlineChart_BecomesReady,\n\t\ttestCreateChartAssignment_WithBadDeployment_BecomesFailed,\n\t\ttestUpdateChartAssignment_WithFixedDeployment_BecomesReady,\n\t\ttestUpdateChartAssignment_WithFixedJob_BecomesReady,\n\t\ttestCreateChartAssignment_CopiesLabelledSecret,\n\t)\n}\n\nfunc testCreateChartAssignment_WithChartReference_Works(t *testing.T, f *kubetest.Fixture) {\n\trobot := f.Client(robotClusterName)\n\n\t// for the version, run:\n\t// helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests\n\t// helm repo update\n\t// helm search oauth2-proxy/oauth2-proxy -l\n\t// Important: we need a V1 chart!\n\ttmpl := `\napiVersion: apps.cloudrobotics.com/v1alpha1\nkind: ChartAssignment\nmetadata:\n  name: {{ .name }}\n  namespace: default\nspec:\n  clusterName: {{ .cluster }}\n  namespaceName: {{ .namespace }}\n  chart:\n    repository: https://oauth2-proxy.github.io/manifests\n    name: oauth2-proxy\n    version: 3.2.6\n    values:\n      fullnameOverride: test\n`\n\tdata := map[string]string{\n\t\t\"cluster\":   robotClusterName,\n\t\t\"name\":      f.Uniq(\"example\"),\n\t\t\"namespace\": f.Uniq(\"ns\"),\n\t}\n\tvar ca crcapps.ChartAssignment\n\tf.FromYAML(tmpl, data, &ca)\n\n\tif err := robot.Create(f.Ctx(), &ca); err != nil {\n\t\tt.Fatalf(\"create ChartAssignment: %s\", err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseSettled),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment settled: %s\", err)\n\t}\n\n\t// We should find a deployment in its own namespace now.\n\tvar dep apps.Deployment\n\tif err := robot.Get(f.Ctx(), client.ObjectKey{\n\t\tNamespace: data[\"namespace\"],\n\t\tName:      \"test\",\n\t}, &dep); err != nil {\n\t\tt.Errorf(\"failed to get deployment: %s\", err)\n\t}\n\t// Chart should've been deployed exactly once.\n\tif want, got := int64(1), ca.Status.ObservedGeneration; want != got {\n\t\tt.Errorf(\"want ca.Status.ObservedGeneration == %d, got %d\", want, got)\n\t}\n}\n\nfunc testCreateChartAssignment_WithInlineChart_BecomesReady(t *testing.T, f *kubetest.Fixture) {\n\trobot := f.Client(robotClusterName)\n\n\tdata := map[string]string{\n\t\t\"cluster\":   robotClusterName,\n\t\t\"name\":      f.Uniq(\"example\"),\n\t\t\"namespace\": f.Uniq(\"ns\"),\n\t\t\"chart\":     kubetest.BuildInlineChart(t, \"example\", goodDeployment /*values=*/, \"\"),\n\t}\n\tvar ca crcapps.ChartAssignment\n\tf.FromYAML(inlineChartTemplate, data, &ca)\n\n\tif err := robot.Create(f.Ctx(), &ca); err != nil {\n\t\tt.Fatalf(\"create ChartAssignment: %s\", err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseReady),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment ready: %s\", err)\n\t}\n}\n\nfunc testCreateChartAssignment_WithBadDeployment_BecomesFailed(t *testing.T, f *kubetest.Fixture) {\n\trobot := f.Client(robotClusterName)\n\n\tdata := map[string]string{\n\t\t\"cluster\":   robotClusterName,\n\t\t\"name\":      f.Uniq(\"example\"),\n\t\t\"namespace\": f.Uniq(\"ns\"),\n\t\t\"chart\":     kubetest.BuildInlineChart(t, \"example\", deploymentWithBadLabels /*values=*/, \"\"),\n\t}\n\tvar ca crcapps.ChartAssignment\n\tf.FromYAML(inlineChartTemplate, data, &ca)\n\n\tif err := robot.Create(f.Ctx(), &ca); err != nil {\n\t\tt.Fatalf(\"create ChartAssignment: %s\", err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseFailed),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment to fail: %s\", err)\n\t}\n}\n\nfunc testUpdateChartAssignment_WithFixedDeployment_BecomesReady(t *testing.T, f *kubetest.Fixture) {\n\trobot := f.Client(robotClusterName)\n\n\t// First, create a bad ChartAssignment and wait for it to fail.\n\tdata := map[string]string{\n\t\t\"cluster\":   robotClusterName,\n\t\t\"name\":      f.Uniq(\"example\"),\n\t\t\"namespace\": f.Uniq(\"ns\"),\n\t\t\"chart\":     kubetest.BuildInlineChart(t, \"example\", deploymentWithBadLabels /*values=*/, \"\"),\n\t}\n\tvar ca crcapps.ChartAssignment\n\tf.FromYAML(inlineChartTemplate, data, &ca)\n\n\tif err := robot.Create(f.Ctx(), &ca); err != nil {\n\t\tt.Fatalf(\"create ChartAssignment: %s\", err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseFailed),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment to fail: %s\", err)\n\t}\n\n\t// Next, fix the ChartAssignment and wait for it to become ready. We have\n\t// to retry the read-modify-write in case the controller updates the status\n\t// in parallel.\n\tif err := backoff.Retry(func() error {\n\t\tif err := robot.Get(f.Ctx(), f.ObjectKey(&ca), &ca); err != nil {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\tca.Spec.Chart.Inline = kubetest.BuildInlineChart(t, \"example\", goodDeployment /*values=*/, \"\")\n\t\tif err := robot.Update(f.Ctx(), &ca); apierrors.IsConflict(err) {\n\t\t\treturn err\n\t\t} else if err != nil {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\treturn nil\n\t}, backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"update ChartAssignment: %s %s\", apierrors.ReasonForError(err), err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseReady),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment to go from Failed to Ready: %s\", err)\n\t}\n}\n\nfunc testUpdateChartAssignment_WithFixedJob_BecomesReady(t *testing.T, f *kubetest.Fixture) {\n\trobot := f.Client(robotClusterName)\n\n\t// First, create a ChartAssignment with a bad job and wait for it to settle.\n\tdata := map[string]string{\n\t\t\"cluster\":   robotClusterName,\n\t\t\"name\":      f.Uniq(\"example\"),\n\t\t\"namespace\": f.Uniq(\"ns\"),\n\t\t\"chart\":     kubetest.BuildInlineChart(t, \"example\", badJob /*values=*/, \"\"),\n\t}\n\tvar ca crcapps.ChartAssignment\n\tf.FromYAML(inlineChartTemplate, data, &ca)\n\n\tif err := robot.Create(f.Ctx(), &ca); err != nil {\n\t\tt.Fatalf(\"create ChartAssignment: %s\", err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseSettled),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment to settle: %s\", err)\n\t}\n\n\t// Next, change the ChartAssignment and wait for it to become ready. We have\n\t// to retry the read-modify-write in case the controller updates the status\n\t// in parallel.\n\tif err := backoff.Retry(func() error {\n\t\tif err := robot.Get(f.Ctx(), f.ObjectKey(&ca), &ca); err != nil {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\tca.Spec.Chart.Inline = kubetest.BuildInlineChart(t, \"example\", goodJob /*values=*/, \"\")\n\t\tif err := robot.Update(f.Ctx(), &ca); apierrors.IsConflict(err) {\n\t\t\treturn err\n\t\t} else if err != nil {\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\treturn nil\n\t}, backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"update ChartAssignment: %s %s\", apierrors.ReasonForError(err), err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseReady),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(3*time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment to go from Settled to Ready: %s\", err)\n\t}\n}\n\nfunc testCreateChartAssignment_CopiesLabelledSecret(t *testing.T, f *kubetest.Fixture) {\n\trobot := f.Client(robotClusterName)\n\tconst secretName = \"my-secret\"\n\tif err := robot.Create(f.Ctx(), &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      secretName,\n\t\t\tNamespace: \"default\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"cloudrobotics.com/copy-to-chart-namespaces\": \"true\",\n\t\t\t},\n\t\t},\n\t\tType: corev1.SecretTypeOpaque,\n\t}); err != nil {\n\t\tt.Fatalf(\"create Secret default/my-secret: %s\", err)\n\t}\n\n\tdata := map[string]string{\n\t\t\"cluster\":   robotClusterName,\n\t\t\"name\":      f.Uniq(\"example\"),\n\t\t\"namespace\": f.Uniq(\"ns\"),\n\t\t\"chart\":     kubetest.BuildInlineChart(t, \"example\", goodDeployment /*values=*/, \"\"),\n\t}\n\tvar ca crcapps.ChartAssignment\n\tf.FromYAML(inlineChartTemplate, data, &ca)\n\tif err := robot.Create(f.Ctx(), &ca); err != nil {\n\t\tt.Fatalf(\"create ChartAssignment: %s\", err)\n\t}\n\n\tif err := backoff.Retry(\n\t\tf.ChartAssignmentHasStatus(&ca, crcapps.ChartAssignmentPhaseSettled),\n\t\tbackoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 60),\n\t); err != nil {\n\t\tt.Fatalf(\"wait for chart assignment settled: %s\", err)\n\t}\n\n\t// We should find a secret in its own namespace now.\n\tvar s corev1.Secret\n\tif err := robot.Get(f.Ctx(), client.ObjectKey{\n\t\tNamespace: data[\"namespace\"],\n\t\tName:      secretName,\n\t}, &s); err != nil {\n\t\tt.Errorf(\"failed to get Secret %s/%s: %s\", data[\"namespace\"], secretName, err)\n\t}\n}\n"
  },
  {
    "path": "src/go/tests/apps/run.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# NOTE: if you are running a minikube using vmdriver=none on the same machine\n# this test (or more precisely kind) does not seem to work - see\n# https://github.com/kubernetes-sigs/kind/issues/2516\n\nset -e\n\necho \"Set NO_TEARDOWN=y for preserve \\\"kind\\\" clusters past test failures\"\necho \"run 'docker ps | grep kind' and 'docker stop <container-id>' to cleanup when done\"\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\nCLOUD_ROBOTICS_CONTAINER_REGISTRY=${CLOUD_ROBOTICS_CONTAINER_REGISTRY:-$1}\nif [[ -z \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\" ]]; then\n  echo \"Usage: $0 <container-registry>\"\n  exit 1\nfi\n\nTAG=\"latest\" bazel run //src/app_charts:push \"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\"\nbazel build //src/go/tests/apps:go_default_test\n\n# Run the artifact directly so the kind clusters can be easily accessed for\n# debugging.\ncd ${DIR}/../../../../bazel-bin/src/go/tests/apps/go_default_test_/go_default_test.runfiles/_main\n\nACCESS_TOKEN=\"$(gcloud auth application-default print-access-token)\" \\\n  REGISTRY=\"${CLOUD_ROBOTICS_CONTAINER_REGISTRY}\" \\\n  ../../go_default_test\n"
  },
  {
    "path": "src/go/tests/k8s_integration_test.go",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"testing\"\n\t\"time\"\n\n\tapps \"github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1\"\n\t\"github.com/googlecloudrobotics/core/src/go/pkg/kubeutils\"\n\t\"github.com/googlecloudrobotics/ilog\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tctrlclient \"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\nconst (\n\tappInitializationTimeout = 7 * time.Minute\n\tpodInitializationTimeout = 5 * time.Minute\n)\n\nfunc checkHealthOfKubernetesCluster(ctx context.Context, kubernetesContext string) error {\n\t// create the kubernetes clientSet\n\tk8sCfg, err := kubeutils.LoadOutOfClusterConfig(kubernetesContext)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Loading of kubernetes config failed: %v\", err)\n\t}\n\tclientSet, err := kubernetes.NewForConfig(k8sCfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Creating the kubernetes client set failed: %v\", err)\n\t}\n\n\tnumNonRunningPods := 0\n\tfailingContainers := 0\n\n\trestartCount := make(map[string]int32)\n\n\ttimeStart := time.Now()\n\n\tfor time.Since(timeStart) < podInitializationTimeout {\n\t\tslog.Info(\"Querying pods...\", slog.String(\"Context\", kubernetesContext))\n\t\tpods, err := clientSet.CoreV1().Pods(\"\").List(ctx, metav1.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Failed to query pods: %v\", err)\n\t\t}\n\t\tslog.Info(\"...done.\", slog.Int(\"PodCount\", len(pods.Items)))\n\n\t\tif len(pods.Items) == 0 {\n\t\t\treturn fmt.Errorf(\"Could not find any pods in cluster\")\n\t\t}\n\n\t\tnumNonRunningPods = 0\n\t\tfailingContainers = 0\n\t\tfor _, pod := range pods.Items {\n\t\t\tslog.Info(\"Pod state\", slog.String(\"Name\", pod.Name), slog.String(\"Phase\", string(pod.Status.Phase)))\n\t\t\tif pod.Status.Phase != \"Running\" && pod.Status.Phase != \"Succeeded\" {\n\t\t\t\tnumNonRunningPods += 1\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\twaitingContainerFound := false\n\t\t\tfor _, container := range pod.Status.ContainerStatuses {\n\t\t\t\t// Exactly one of Running/Terminated/Waiting in container.State is set\n\t\t\t\tif container.State.Running != nil {\n\t\t\t\t\trestartKey := pod.Name + container.Name\n\t\t\t\t\tprevRestarts, ok := restartCount[restartKey]\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tprevRestarts = 0\n\t\t\t\t\t}\n\t\t\t\t\tif container.RestartCount > prevRestarts {\n\t\t\t\t\t\tslog.Warn(\"Container restarted\",\n\t\t\t\t\t\t\tslog.String(\"Pod\", pod.Name),\n\t\t\t\t\t\t\tslog.String(\"Container\", container.Name),\n\t\t\t\t\t\t\tslog.String(\"Image\", container.Image),\n\t\t\t\t\t\t\tslog.Int(\"RestartCount\", int(container.RestartCount)))\n\t\t\t\t\t\tfailingContainers += 1\n\t\t\t\t\t}\n\t\t\t\t\trestartCount[restartKey] = container.RestartCount\n\t\t\t\t} else if container.State.Terminated != nil && container.State.Terminated.ExitCode != 0 {\n\t\t\t\t\tslog.Warn(\"Container terminated\",\n\t\t\t\t\t\tslog.String(\"Pod\", pod.Name),\n\t\t\t\t\t\tslog.String(\"Container\", container.Name),\n\t\t\t\t\t\tslog.String(\"Image\", container.Image),\n\t\t\t\t\t\tslog.Int(\"RestartCount\", int(container.RestartCount)))\n\t\t\t\t\tfailingContainers += 1\n\t\t\t\t} else if container.State.Waiting != nil {\n\t\t\t\t\tslog.Warn(\"Container waiting\",\n\t\t\t\t\t\tslog.String(\"Pod\", pod.Name),\n\t\t\t\t\t\tslog.String(\"Container\", container.Name),\n\t\t\t\t\t\tslog.String(\"Image\", container.Image),\n\t\t\t\t\t\tslog.Int(\"RestartCount\", int(container.RestartCount)))\n\t\t\t\t\twaitingContainerFound = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif waitingContainerFound {\n\t\t\t\tnumNonRunningPods += 1\n\t\t\t}\n\t\t}\n\n\t\tif numNonRunningPods == 0 && failingContainers == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(10 * time.Second)\n\t}\n\n\tif numNonRunningPods != 0 || failingContainers != 0 {\n\t\treturn fmt.Errorf(\"Unhealthy cluster status after waiting for %d sec: %d non-running pods, %d failing containers\\n\",\n\t\t\tpodInitializationTimeout/time.Second, numNonRunningPods, failingContainers)\n\t}\n\tslog.Info(\"All pods are happily running :)\")\n\treturn nil\n}\n\n// convert a resource from one type representation to another one.\nfunc convert(from, to runtime.Object) error {\n\tb, err := json.Marshal(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(b, &to)\n}\n\nfunc TestCloudClusterAppStatus(t *testing.T) {\n\tkubernetesContext, err := kubeutils.GetCloudKubernetesContext()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tk8sCfg, err := kubeutils.LoadOutOfClusterConfig(kubernetesContext)\n\tif err != nil {\n\t\tt.Errorf(\"Loading of kubernetes config failed: %v\", err)\n\t}\n\n\tsc := runtime.NewScheme()\n\tscheme.AddToScheme(sc)\n\tapps.AddToScheme(sc)\n\n\tclient, err := ctrlclient.New(k8sCfg, ctrlclient.Options{Scheme: sc})\n\tif err != nil {\n\t\tt.Errorf(\"Failed to create kubernetes client: %v\", err)\n\t}\n\n\tnumBadConditions := 0\n\n\ttimeStart := time.Now()\n\n\tfor time.Since(timeStart) < appInitializationTimeout {\n\t\tappRollouts := &unstructured.UnstructuredList{}\n\t\tappRollouts.SetGroupVersionKind(schema.GroupVersionKind{\n\t\t\tGroup:   \"apps.cloudrobotics.com\",\n\t\t\tKind:    \"AppRollout\",\n\t\t\tVersion: \"v1alpha1\",\n\t\t})\n\t\tslog.Info(\"Querying AppRollouts...\", slog.String(\"Context\", kubernetesContext))\n\t\terr = client.List(context.Background(), appRollouts)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Failed to list AppRollouts\", ilog.Err(err))\n\t\t\ttime.Sleep(10 * time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tslog.Info(\"...done.\", slog.Int(\"AppRolloutCount\", len(appRollouts.Items)))\n\n\t\tnumBadConditions = 0\n\t\tfor _, i := range appRollouts.Items {\n\t\t\tar := &apps.AppRollout{}\n\t\t\tif err := convert(&i, ar); err != nil {\n\t\t\t\tt.Errorf(\"Failed to unmarshall AppRollout: %v\", err)\n\t\t\t}\n\t\t\tfor _, c := range ar.Status.Conditions {\n\t\t\t\tslog.Info(\"AppRollout condition\", slog.String(\"Name\", ar.GetName()), slog.String(\"Condition\", string(c.Type)), slog.String(\"Status\", string(c.Status)))\n\t\t\t\tif c.Status != corev1.ConditionTrue {\n\t\t\t\t\tslog.Warn(\"AppRollout condition not met\", slog.String(\"Name\", ar.GetName()), slog.String(\"Condition\", string(c.Type)))\n\t\t\t\t\tnumBadConditions += 1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif numBadConditions == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(15 * time.Second)\n\t}\n\tif numBadConditions != 0 {\n\t\tt.Errorf(\"Unhealthy AppRollout status after waiting for %d sec: %d conditions not met\\n\",\n\t\t\tappInitializationTimeout/time.Second, numBadConditions)\n\t}\n}\n\nfunc TestKubernetesCloudClusterStatus(t *testing.T) {\n\tctx := context.Background()\n\tkubernetesCloudContext, err := kubeutils.GetCloudKubernetesContext()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif err := checkHealthOfKubernetesCluster(ctx, kubernetesCloudContext); err != nil {\n\t\tt.Errorf(\"Cloud cluster %s: %v\", kubernetesCloudContext, err)\n\t}\n}\n\nfunc TestKubernetesRobotClusterStatus(t *testing.T) {\n\tctx := context.Background()\n\tkubernetesRobotContext, err := kubeutils.GetRobotKubernetesContext()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif err := checkHealthOfKubernetesCluster(ctx, kubernetesRobotContext); err != nil {\n\t\tt.Errorf(\"Robot cluster %s: %v\", kubernetesRobotContext, err)\n\t}\n\n}\n"
  },
  {
    "path": "src/go/tests/k8s_integration_test_auth_helper.go",
    "content": "/*\nCopyright 2016 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// GIANT NOTE: methylDragon@\n//   This is a verbatim copy of the state of the Kubernetes auth provider before it was REMOVED in\n//   https://github.com/kubernetes/client-go/commit/7d208ba573ecc2c6294482b51872021718f76cb4\n//\n//   With migration recommendation:\n//   https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke\n//\n//   Unfortunately, despite installing the package as per that recommendation in the cloudbuild machine,\n//   the test doesn't seem to accept the plugin. (It could be that it is running in a different machine.)\n//   So the plugin is reintroduced here to allow for GKE auth to proceed as part of a cloudbuild test.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\t\"k8s.io/apimachinery/pkg/util/net\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\trestclient \"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/util/jsonpath\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc init() {\n\tif err := restclient.RegisterAuthProviderPlugin(\"gcp\", newGCPAuthProvider); err != nil {\n\t\tklog.Fatalf(\"Failed to register gcp auth plugin: %v\", err)\n\t}\n}\n\nvar (\n\t// Stubbable for testing\n\texecCommand = exec.Command\n\n\t// defaultScopes:\n\t// - cloud-platform is the base scope to authenticate to GCP.\n\t// - userinfo.email is used to authenticate to GKE APIs with gserviceaccount\n\t//   email instead of numeric uniqueID.\n\tdefaultScopes = []string{\n\t\t\"https://www.googleapis.com/auth/cloud-platform\",\n\t\t\"https://www.googleapis.com/auth/userinfo.email\"}\n)\n\n// gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide\n// tokens for kubectl to authenticate itself to the apiserver. A sample json config\n// is provided below with all recognized options described.\n//\n//\t{\n//\t  'auth-provider': {\n//\t    # Required\n//\t    \"name\": \"gcp\",\n//\n//\t    'config': {\n//\t      # Authentication options\n//\t      # These options are used while getting a token.\n//\n//\t      # comma-separated list of GCP API scopes. default value of this field\n//\t      # is \"https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email\".\n//\t\t\t # to override the API scopes, specify this field explicitly.\n//\t      \"scopes\": \"https://www.googleapis.com/auth/cloud-platform\"\n//\n//\t      # Caching options\n//\n//\t      # Raw string data representing cached access token.\n//\t      \"access-token\": \"ya29.CjWdA4GiBPTt\",\n//\t      # RFC3339Nano expiration timestamp for cached access token.\n//\t      \"expiry\": \"2016-10-31 22:31:9.123\",\n//\n//\t      # Command execution options\n//\t      # These options direct the plugin to execute a specified command and parse\n//\t      # token and expiry time from the output of the command.\n//\n//\t      # Command to execute for access token. Command output will be parsed as JSON.\n//\t      # If \"cmd-args\" is not present, this value will be split on whitespace, with\n//\t      # the first element interpreted as the command, remaining elements as args.\n//\t      \"cmd-path\": \"/usr/bin/gcloud\",\n//\n//\t      # Arguments to pass to command to execute for access token.\n//\t      \"cmd-args\": \"config config-helper --output=json\"\n//\n//\t      # JSONPath to the string field that represents the access token in\n//\t      # command output. If omitted, defaults to \"{.access_token}\".\n//\t      \"token-key\": \"{.credential.access_token}\",\n//\n//\t      # JSONPath to the string field that represents expiration timestamp\n//\t      # of the access token in the command output. If omitted, defaults to\n//\t      # \"{.token_expiry}\"\n//\t      \"expiry-key\": \"\"{.credential.token_expiry}\",\n//\n//\t      # golang reference time in the format that the expiration timestamp uses.\n//\t      # If omitted, defaults to time.RFC3339Nano\n//\t      \"time-fmt\": \"2006-01-02 15:04:05.999999999\"\n//\t    }\n//\t  }\n//\t}\ntype gcpAuthProvider struct {\n\ttokenSource oauth2.TokenSource\n\tpersister   restclient.AuthProviderConfigPersister\n}\n\nvar warnOnce sync.Once\n\nfunc newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {\n\twarnOnce.Do(func() {\n\t\tklog.Warningf(`WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.26+; use gcloud instead.\nTo learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke`)\n\t})\n\n\tts, err := tokenSource(isCmdTokenSource(gcpConfig), gcpConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcts, err := newCachedTokenSource(gcpConfig[\"access-token\"], gcpConfig[\"expiry\"], persister, ts, gcpConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &gcpAuthProvider{cts, persister}, nil\n}\n\nfunc isCmdTokenSource(gcpConfig map[string]string) bool {\n\t_, ok := gcpConfig[\"cmd-path\"]\n\treturn ok\n}\n\nfunc tokenSource(isCmd bool, gcpConfig map[string]string) (oauth2.TokenSource, error) {\n\t// Command-based token source\n\tif isCmd {\n\t\tcmd := gcpConfig[\"cmd-path\"]\n\t\tif len(cmd) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"missing access token cmd\")\n\t\t}\n\t\tif gcpConfig[\"scopes\"] != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"scopes can only be used when kubectl is using a gcp service account key\")\n\t\t}\n\t\tvar args []string\n\t\tif cmdArgs, ok := gcpConfig[\"cmd-args\"]; ok {\n\t\t\targs = strings.Fields(cmdArgs)\n\t\t} else {\n\t\t\tfields := strings.Fields(cmd)\n\t\t\tcmd = fields[0]\n\t\t\targs = fields[1:]\n\t\t}\n\t\treturn newCmdTokenSource(cmd, args, gcpConfig[\"token-key\"], gcpConfig[\"expiry-key\"], gcpConfig[\"time-fmt\"]), nil\n\t}\n\n\t// Google Application Credentials-based token source\n\tscopes := parseScopes(gcpConfig)\n\tts, err := google.DefaultTokenSource(context.Background(), scopes...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot construct google default token source: %v\", err)\n\t}\n\treturn ts, nil\n}\n\n// parseScopes constructs a list of scopes that should be included in token source\n// from the config map.\nfunc parseScopes(gcpConfig map[string]string) []string {\n\tscopes, ok := gcpConfig[\"scopes\"]\n\tif !ok {\n\t\treturn defaultScopes\n\t}\n\tif scopes == \"\" {\n\t\treturn []string{}\n\t}\n\treturn strings.Split(gcpConfig[\"scopes\"], \",\")\n}\n\nfunc (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {\n\tvar resetCache map[string]string\n\tif cts, ok := g.tokenSource.(*cachedTokenSource); ok {\n\t\tresetCache = cts.baseCache()\n\t} else {\n\t\tresetCache = make(map[string]string)\n\t}\n\treturn &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister, resetCache}\n}\n\nfunc (g *gcpAuthProvider) Login() error { return nil }\n\ntype cachedTokenSource struct {\n\tlk          sync.Mutex\n\tsource      oauth2.TokenSource\n\taccessToken string `datapolicy:\"token\"`\n\texpiry      time.Time\n\tpersister   restclient.AuthProviderConfigPersister\n\tcache       map[string]string\n}\n\nfunc newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister, ts oauth2.TokenSource, cache map[string]string) (*cachedTokenSource, error) {\n\tvar expiryTime time.Time\n\tif parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil {\n\t\texpiryTime = parsedTime\n\t}\n\tif cache == nil {\n\t\tcache = make(map[string]string)\n\t}\n\treturn &cachedTokenSource{\n\t\tsource:      ts,\n\t\taccessToken: accessToken,\n\t\texpiry:      expiryTime,\n\t\tpersister:   persister,\n\t\tcache:       cache,\n\t}, nil\n}\n\nfunc (t *cachedTokenSource) Token() (*oauth2.Token, error) {\n\ttok := t.cachedToken()\n\tif tok.Valid() && !tok.Expiry.IsZero() {\n\t\treturn tok, nil\n\t}\n\ttok, err := t.source.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcache := t.update(tok)\n\tif t.persister != nil {\n\t\tif err := t.persister.Persist(cache); err != nil {\n\t\t\tklog.V(4).Infof(\"Failed to persist token: %v\", err)\n\t\t}\n\t}\n\treturn tok, nil\n}\n\nfunc (t *cachedTokenSource) cachedToken() *oauth2.Token {\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\treturn &oauth2.Token{\n\t\tAccessToken: t.accessToken,\n\t\tTokenType:   \"Bearer\",\n\t\tExpiry:      t.expiry,\n\t}\n}\n\nfunc (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\tt.accessToken = tok.AccessToken\n\tt.expiry = tok.Expiry\n\tret := map[string]string{}\n\tfor k, v := range t.cache {\n\t\tret[k] = v\n\t}\n\tret[\"access-token\"] = t.accessToken\n\tret[\"expiry\"] = t.expiry.Format(time.RFC3339Nano)\n\treturn ret\n}\n\n// baseCache is the base configuration value for this TokenSource, without any cached ephemeral tokens.\nfunc (t *cachedTokenSource) baseCache() map[string]string {\n\tt.lk.Lock()\n\tdefer t.lk.Unlock()\n\tret := map[string]string{}\n\tfor k, v := range t.cache {\n\t\tret[k] = v\n\t}\n\tdelete(ret, \"access-token\")\n\tdelete(ret, \"expiry\")\n\treturn ret\n}\n\ntype commandTokenSource struct {\n\tcmd       string\n\targs      []string\n\ttokenKey  string `datapolicy:\"token\"`\n\texpiryKey string `datapolicy:\"secret-key\"`\n\ttimeFmt   string\n}\n\nfunc newCmdTokenSource(cmd string, args []string, tokenKey, expiryKey, timeFmt string) *commandTokenSource {\n\tif len(timeFmt) == 0 {\n\t\ttimeFmt = time.RFC3339Nano\n\t}\n\tif len(tokenKey) == 0 {\n\t\ttokenKey = \"{.access_token}\"\n\t}\n\tif len(expiryKey) == 0 {\n\t\texpiryKey = \"{.token_expiry}\"\n\t}\n\treturn &commandTokenSource{\n\t\tcmd:       cmd,\n\t\targs:      args,\n\t\ttokenKey:  tokenKey,\n\t\texpiryKey: expiryKey,\n\t\ttimeFmt:   timeFmt,\n\t}\n}\n\nfunc (c *commandTokenSource) Token() (*oauth2.Token, error) {\n\tfullCmd := strings.Join(append([]string{c.cmd}, c.args...), \" \")\n\tcmd := execCommand(c.cmd, c.args...)\n\tvar stderr bytes.Buffer\n\tcmd.Stderr = &stderr\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error executing access token command %q: err=%v output=%s stderr=%s\", fullCmd, err, output, string(stderr.Bytes()))\n\t}\n\ttoken, err := c.parseTokenCmdOutput(output)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing output for access token command %q: %v\", fullCmd, err)\n\t}\n\treturn token, nil\n}\n\nfunc (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token, error) {\n\toutput, err := yaml.ToJSON(output)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar data interface{}\n\tif err := json.Unmarshal(output, &data); err != nil {\n\t\treturn nil, err\n\t}\n\n\taccessToken, err := parseJSONPath(data, \"token-key\", c.tokenKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing token-key %q from %q: %v\", c.tokenKey, string(output), err)\n\t}\n\texpiryStr, err := parseJSONPath(data, \"expiry-key\", c.expiryKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing expiry-key %q from %q: %v\", c.expiryKey, string(output), err)\n\t}\n\tvar expiry time.Time\n\tif t, err := time.Parse(c.timeFmt, expiryStr); err != nil {\n\t\tklog.V(4).Infof(\"Failed to parse token expiry from %s (fmt=%s): %v\", expiryStr, c.timeFmt, err)\n\t} else {\n\t\texpiry = t\n\t}\n\n\treturn &oauth2.Token{\n\t\tAccessToken: accessToken,\n\t\tTokenType:   \"Bearer\",\n\t\tExpiry:      expiry,\n\t}, nil\n}\n\nfunc parseJSONPath(input interface{}, name, template string) (string, error) {\n\tj := jsonpath.New(name)\n\tbuf := new(bytes.Buffer)\n\tif err := j.Parse(template); err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := j.Execute(buf, input); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn buf.String(), nil\n}\n\ntype conditionalTransport struct {\n\toauthTransport *oauth2.Transport\n\tpersister      restclient.AuthProviderConfigPersister\n\tresetCache     map[string]string\n}\n\nvar _ net.RoundTripperWrapper = &conditionalTransport{}\n\nfunc (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\tif len(req.Header.Get(\"Authorization\")) != 0 {\n\t\treturn t.oauthTransport.Base.RoundTrip(req)\n\t}\n\n\tres, err := t.oauthTransport.RoundTrip(req)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif res.StatusCode == 401 {\n\t\tklog.V(4).Infof(\"The credentials that were supplied are invalid for the target cluster\")\n\t\tt.persister.Persist(t.resetCache)\n\t}\n\n\treturn res, nil\n}\n\nfunc (t *conditionalTransport) WrappedRoundTripper() http.RoundTripper { return t.oauthTransport.Base }\n"
  },
  {
    "path": "src/go/tests/relay/BUILD.bazel",
    "content": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_test\")\n\n# gazelle:go_test file\n\ngo_test(\n    name = \"in_process_relay_test\",\n    size = \"small\",\n    srcs = [\"in_process_relay_test.go\"],\n    # https://github.com/googlecloudrobotics/core/issues/507\n    flaky = True,\n    deps = [\n        \"//src/go/cmd/http-relay-client/client:go_default_library\",\n        \"//src/go/cmd/http-relay-server/server:go_default_library\",\n        \"@com_github_golang_glog//:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"nok8s_relay_test\",\n    size = \"small\",\n    srcs = [\"nok8s_relay_test.go\"],\n    data = [\n        \"//src/go/cmd/http-relay-client:http-relay-client-app\",\n        \"//src/go/cmd/http-relay-server:http-relay-server-app\",\n    ],\n    rundir = \".\",\n    deps = [\n        \"//src/go/cmd/http-relay-client/client:go_default_library\",\n        \"@com_github_pkg_errors//:go_default_library\",\n        \"@org_golang_google_grpc//:go_default_library\",\n        \"@org_golang_google_grpc//codes:go_default_library\",\n        \"@org_golang_google_grpc//interop/grpc_testing:go_default_library\",\n        \"@org_golang_google_grpc//metadata:go_default_library\",\n        \"@org_golang_google_grpc//status:go_default_library\",\n        \"@org_golang_x_net//websocket:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "src/go/tests/relay/in_process_relay_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang/glog\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-client/client\"\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-server/server\"\n)\n\nvar (\n\trelayPort   int // Will be initialized by initRelay()\n\tbackendPort int // Will be initialized by initRelay()\n\tblockSize   = 10 * 1024\n\tonce        sync.Once\n)\n\nfunc pickUnusedPortOrDie() int {\n\tvar addr *net.TCPAddr\n\tvar err error\n\tif addr, err = net.ResolveTCPAddr(\"tcp\", \"localhost:0\"); err == nil {\n\t\tvar list *net.TCPListener\n\t\tif list, err = net.ListenTCP(\"tcp\", addr); err == nil {\n\t\t\tdefer list.Close()\n\t\t\treturn list.Addr().(*net.TCPAddr).Port\n\t\t}\n\t}\n\tglog.Fatal(\"Failed to pick a free TCP port.\")\n\treturn 0\n}\n\nfunc initRelay() {\n\tonce.Do(func() {\n\t\tglog.Info(\"Running init.\")\n\n\t\tbackendPort = pickUnusedPortOrDie()\n\t\trelayPort = pickUnusedPortOrDie()\n\n\t\tglog.Infof(\"Setting up relay.\\n\\tBackend port: %d\\n\\tRelay port: %d\", backendPort, relayPort)\n\n\t\tgo func() {\n\t\t\trelayServer := server.NewServer(server.Config{\n\t\t\t\tPort:      relayPort,\n\t\t\t\tBlockSize: blockSize,\n\t\t\t})\n\t\t\trelayServer.Start()\n\t\t}()\n\n\t\tgo func() {\n\t\t\tconfig := client.DefaultClientConfig()\n\t\t\tconfig.RelayScheme = \"http\"\n\t\t\tconfig.RelayAddress = fmt.Sprint(\"127.0.0.1:\", relayPort)\n\t\t\tconfig.BackendScheme = \"http\"\n\t\t\tconfig.BackendAddress = fmt.Sprint(\"127.0.0.1:\", backendPort)\n\t\t\tconfig.DisableAuthForRemote = true\n\t\t\trelayClient := client.NewClient(config)\n\t\t\trelayClient.Start()\n\t\t}()\n\n\t\trelayHealthy := false\n\t\tdeadline := time.Now().Add(5 * time.Second)\n\t\tfor time.Now().Before(deadline) {\n\t\t\trelayHealthzAddr := fmt.Sprint(\"http://127.0.0.1:\", relayPort, \"/healthz\")\n\t\t\tres, err := http.Get(relayHealthzAddr)\n\t\t\tif err != nil {\n\t\t\t\tglog.Infof(\"Relay server is has not yet started, retrying.\")\n\t\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t\t} else {\n\t\t\t\tglog.Info(\"Relay server is up and running.\")\n\t\t\t\trelayHealthy = true\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tio.ReadAll(res.Body)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !relayHealthy {\n\t\t\tglog.Fatal(\"Failed to bring up http relay for unknown reason.\")\n\t\t}\n\t})\n}\n\nfunc serveFunction(\n\tf func(w http.ResponseWriter, r *http.Request)) *http.Server {\n\treturn serveFunctionWithTimeout(f, 10*time.Second)\n}\n\nfunc serveFunctionWithTimeout(\n\tf func(w http.ResponseWriter, r *http.Request),\n\thandlerTimeout time.Duration) *http.Server {\n\thttpHandlerFunc := http.HandlerFunc(f)\n\n\tsrv := &http.Server{\n\t\tAddr:         fmt.Sprint(\"127.0.0.1:\", backendPort),\n\t\tReadTimeout:  5 * time.Second, // Time between accepted connection and request body being read.\n\t\tWriteTimeout: 5 * time.Second, // Time between request header being read and response body being written.\n\t\tHandler:      http.TimeoutHandler(httpHandlerFunc, handlerTimeout, \"Timeout\"),\n\t}\n\n\tgo func() {\n\t\tsrv.ListenAndServe()\n\t}()\n\n\treturn srv\n}\n\nfunc TestHttpResponse(t *testing.T) {\n\tinitRelay()\n\n\texpectedResponse := []byte(\"Unit test response.\")\n\n\t// Setup a backend function which just serves a string.\n\thttpServer := serveFunction(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write(expectedResponse)\n\t})\n\tdefer httpServer.Shutdown(context.Background())\n\n\t// Invoke the backend function through the relay.\n\trelayAddress := fmt.Sprint(\"http://127.0.0.1:\", relayPort, \"/client/server_name/\")\n\tres, err := http.Get(relayAddress)\n\tif err != nil {\n\t\tt.Error(\"Server responeded with an error. Error %v\", err)\n\t}\n\tdefer res.Body.Close()\n\tobservedResponse, err := io.ReadAll(res.Body)\n\tif !bytes.Equal(observedResponse, expectedResponse) {\n\t\tt.Errorf(\"Received wrong response.\\n\\tExpected: %s\\n\\tObserved: %s\", expectedResponse, observedResponse)\n\t}\n}\n\nfunc TestHttpTimeout(t *testing.T) {\n\tinitRelay()\n\n\t// Setup a backend server which will create a timeout and result in a 503.\n\thttpServer := serveFunctionWithTimeout(func(w http.ResponseWriter, r *http.Request) {\n\t\ttime.Sleep(2 * time.Second)\n\t}, 1*time.Second)\n\tdefer httpServer.Shutdown(context.Background())\n\n\t// Hit the backend function through the relay and verify that the relay\n\t// forwards the 503 timeout.\n\trelayAddress := fmt.Sprint(\"http://127.0.0.1:\", relayPort, \"/client/server_name/\")\n\tres, err := http.Get(relayAddress)\n\tif err != nil {\n\t\tt.Errorf(\"Server responded with an error. Error: %+v\", err)\n\t\treturn\n\t}\n\tif res.StatusCode != 503 {\n\t\tt.Error(\"No timeout error received.\")\n\t}\n}\n\nfunc TestHttpErrorPropagation(t *testing.T) {\n\tinitRelay()\n\n\ttests := []struct {\n\t\tname       string\n\t\tstatusCode int\n\t}{\n\t\t{\"Propagate http.StatusBadRequest\", http.StatusBadRequest},                           // 400\n\t\t{\"Propagate http.StatusUnauthorized\", http.StatusUnauthorized},                       // 401\n\t\t{\"Propagate http.StatusPaymentRequired\", http.StatusPaymentRequired},                 // 402\n\t\t{\"Propagate http.StatusForbidden\", http.StatusForbidden},                             // 403\n\t\t{\"Propagate http.StatusNotFound\", http.StatusNotFound},                               // 404\n\t\t{\"Propagate http.StatusMethodNotAllowed\", http.StatusMethodNotAllowed},               // 405\n\t\t{\"Propagate http.StatusInternalServerError\", http.StatusInternalServerError},         // 500\n\t\t{\"Propagate http.StatusNotImplemented\", http.StatusNotImplemented},                   // 501\n\t\t{\"Propagate http.StatusBadGateway\", http.StatusBadGateway},                           // 502\n\t\t{\"Propagate http.StatusServiceUnavailable\", http.StatusServiceUnavailable},           // 503\n\t\t{\"Propagate http.StatusGatewayTimeout\", http.StatusGatewayTimeout},                   // 504\n\t\t{\"Propagate http.StatusHTTPVersionNotSupported\", http.StatusHTTPVersionNotSupported}, // 505\n\t}\n\n\tfor _, test := range tests {\n\t\t// Invoke a sub-test\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// Setup a backend function which just serves an error code.\n\t\t\thttpServer := serveFunction(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.WriteHeader(test.statusCode)\n\t\t\t})\n\t\t\tdefer httpServer.Shutdown(context.Background())\n\n\t\t\t// Invoke the backend function through the relay.\n\t\t\trelayAddress := fmt.Sprint(\"http://127.0.0.1:\", relayPort, \"/client/server_name/\")\n\t\t\tres, err := http.Get(relayAddress)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Server responeded with an error. Error %v\", err)\n\t\t\t}\n\t\t\tif res.StatusCode != test.statusCode {\n\t\t\t\tt.Errorf(\"Server responeded with an unexpected status code.\\n\\tExpected: %v\\n\\tObserved: %v\", test.statusCode, res.StatusCode)\n\t\t\t}\n\t\t\tdefer res.Body.Close()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/go/tests/relay/nok8s_relay_test.go",
    "content": "// Copyright 2022 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/googlecloudrobotics/core/src/go/cmd/http-relay-client/client\"\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/net/websocket\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\ttestpb \"google.golang.org/grpc/interop/grpc_testing\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\nconst (\n\tRelayClientPath = \"src/go/cmd/http-relay-client/http-relay-client-app_/http-relay-client-app\"\n\tRelayServerPath = \"src/go/cmd/http-relay-server/http-relay-server-app_/http-relay-server-app\"\n)\n\nvar (\n\tRelayClientArgs = []string{\n\t\t\"--backend_scheme=http\",\n\t\t\"--relay_scheme=http\",\n\t\t\"--server_name=remote1\",\n\t\t\"--disable_auth_for_remote\",\n\t}\n\tRelayServerArgs = []string{\n\t\t\"--port=0\",\n\t}\n\trsPortMatcher = regexp.MustCompile(`Relay server listening.*\"Port\":(\\d+)`)\n)\n\ntype relay struct {\n\trs, rc *exec.Cmd\n\trsPort string\n}\n\n// start brings up the relay processes\nfunc (r *relay) start(backendAddress string, extraClientArgs ...string) error {\n\t// run relay server exposing the relay client\n\tvar rsOut bytes.Buffer\n\tr.rs = exec.Command(RelayServerPath, RelayServerArgs...)\n\tr.rs.Stdout = os.Stdout\n\tr.rs.Stderr = io.MultiWriter(os.Stderr, &rsOut)\n\tif err := r.rs.Start(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to start relay-server\")\n\t}\n\tr.rsPort = \"\"\n\tfor i := 0; i < 10; i++ {\n\t\tslog.Info(\"Output\", slog.String(\"Output\", rsOut.String()))\n\t\tif m := rsPortMatcher.FindStringSubmatch(rsOut.String()); m != nil {\n\t\t\tr.rsPort = m[1]\n\t\t\tslog.Info(\"Server port\", slog.String(\"Port\", r.rsPort))\n\t\t\tbreak\n\t\t}\n\t\tslog.Info(\"Waiting for relay to be up-and-running ...\")\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\tif r.rsPort == \"\" {\n\t\treturn errors.New(\"timeout waiting for relay-server to launch\")\n\t}\n\n\t// run relay client exposing the test-backend\n\trcArgs := append(RelayClientArgs, []string{\n\t\t\"--backend_address=\" + backendAddress,\n\t\t\"--relay_address=127.0.0.1:\" + r.rsPort,\n\t}...)\n\trcArgs = append(rcArgs, extraClientArgs...)\n\tslog.Info(\"Starting backend\", slog.String(\"Address\", backendAddress))\n\n\tr.rc = exec.Command(RelayClientPath, rcArgs...)\n\tr.rc.Stdout = os.Stdout\n\tr.rc.Stderr = os.Stderr\n\tif err := r.rc.Start(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to start relay-client\")\n\t}\n\n\tconnected := false\n\tfor i := 0; i < 10; i++ {\n\t\tif strings.Contains(rsOut.String(), \"Relay client connected\") {\n\t\t\tconnected = true\n\t\t\tbreak\n\t\t}\n\t\tslog.Info(\"Waiting for relay to be up-and-running ...\")\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\tif !connected {\n\t\terrors.New(\"timeout waiting for relay-client to connect to relay-server\")\n\t}\n\treturn nil\n}\n\n// stop tears down the relay processes\nfunc (r *relay) stop() error {\n\tif err := r.rs.Process.Kill(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to kill relay-server\")\n\t}\n\tif err := r.rc.Process.Kill(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to kill relay-client\")\n\t}\n\treturn nil\n}\n\n// TestHttpRelay launches a local http relay (client + server) and connects a\n// test-http-server as a backend. The test is then interacting with the backend\n// through the local relay.\nfunc TestHttpRelay(t *testing.T) {\n\ttests := []struct {\n\t\tdesc       string\n\t\turlPath    string\n\t\tstatusCode int\n\t\tbody       string\n\t}{\n\t\t{\n\t\t\tdesc:       \"simple get\",\n\t\t\turlPath:    \"/client/remote1/\",\n\t\t\tstatusCode: http.StatusOK,\n\t\t\tbody:       \"Hello\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"backend status is preserved\",\n\t\t\turlPath:    \"/client/remote1/bad-path\",\n\t\t\tstatusCode: http.StatusNotFound,\n\t\t\tbody:       \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"invalid client\",\n\t\t\turlPath:    \"/client/wrong/\",\n\t\t\tstatusCode: http.StatusServiceUnavailable,\n\t\t\tbody:       \"\",\n\t\t},\n\t}\n\n\t// setup http test server\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/\" {\n\t\t\tfmt.Fprintln(w, \"Hello\")\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}))\n\tdefer ts.Close()\n\n\tbackendAddress := strings.TrimPrefix(ts.URL, \"http://\")\n\tr := &relay{}\n\tif err := r.start(backendAddress); err != nil {\n\t\tt.Fatal(\"failed to start relay: \", err)\n\t}\n\tdefer func() {\n\t\tif err := r.stop(); err != nil {\n\t\t\tt.Fatal(\"failed to stop relay: \", err)\n\t\t}\n\t}()\n\trelayAddress := \"http://127.0.0.1:\" + r.rsPort\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tres, err := http.Get(relayAddress + tc.urlPath)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(res.Body)\n\t\t\tres.Body.Close()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif res.StatusCode != tc.statusCode {\n\t\t\t\tt.Errorf(\"Wrong status code - got %d, expected %d\", res.StatusCode, tc.statusCode)\n\t\t\t}\n\t\t\tif !strings.Contains(string(body), tc.body) {\n\t\t\t\tt.Errorf(\"Wrong body - got %q, expected it to contain %q, \", body, tc.body)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestDroppedUserClientFreesRelayChannel checks that when the user client closes a connection,\n// it is propagated to the relay server and client, closing the backend connection as well.\nfunc TestDroppedUserClientFreesRelayChannel(t *testing.T) {\n\t// setup http test server\n\tconnClosed := make(chan error)\n\tdefer close(connClosed)\n\tfinishServer := make(chan bool)\n\tdefer close(finishServer)\n\n\t// mock a long running backend that uses chunking to send periodic updates\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-finishServer:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tif _, err := fmt.Fprintln(w, \"DEADBEEF\"); err != nil {\n\t\t\t\t\tconnClosed <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif flusher, ok := w.(http.Flusher); ok {\n\t\t\t\t\tflusher.Flush()\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(\"cannot flush\")\n\t\t\t\t}\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t}\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tbackendAddress := strings.TrimPrefix(ts.URL, \"http://\")\n\tr := &relay{}\n\tif err := r.start(backendAddress); err != nil {\n\t\tt.Fatal(\"failed to start relay: \", err)\n\t}\n\tdefer r.stop()\n\trelayAddress := \"http://127.0.0.1:\" + r.rsPort\n\n\tres, err := http.Get(relayAddress + \"/client/remote1/\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// receive the first chunk then terminates the connection\n\tif _, err := bufio.NewReader(res.Body).ReadString('\\n'); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tres.Body.Close()\n\n\t// wait for up to 30s for backend connection to be closed\n\tselect {\n\tcase <-connClosed:\n\tcase <-time.After(30 * time.Second):\n\t\tt.Error(\"Server did not close connection\")\n\t}\n}\n\n// TestDroppedBidiStreamFreesRelayChannel checks that when a bidi stream (websockets) closes,\n// it is propagated to the relay server and client, closing the backend connection as well.\nfunc TestDroppedBidiStreamFreesRelayChannel(t *testing.T) {\n\t// setup http test server\n\tdone := make(chan error)\n\tdefer close(done)\n\n\t// test websockets server that echo received messages\n\tts := httptest.NewServer(websocket.Handler(func(conn *websocket.Conn) {\n\t\tr := bufio.NewReader(conn)\n\t\tfor {\n\t\t\treq, err := r.ReadBytes('\\n')\n\t\t\tif err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tdone <- nil\n\t\t\t\t} else {\n\t\t\t\t\tdone <- err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif _, err := conn.Write(req); err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tdone <- nil\n\t\t\t\t} else {\n\t\t\t\t\tdone <- err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}))\n\tdefer func() {\n\t\tts.CloseClientConnections()\n\t\tts.Close()\n\t}()\n\n\tbackendAddress := strings.TrimPrefix(ts.URL, \"http://\")\n\tr := &relay{}\n\tif err := r.start(backendAddress); err != nil {\n\t\tt.Fatal(\"failed to start relay: \", err)\n\t}\n\tdefer r.stop()\n\trelayAddress := \"ws://127.0.0.1:\" + r.rsPort\n\n\tclientConn, err := websocket.Dial(relayAddress+\"/client/remote1/\", \"\", \"http://127.0.0.1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// receive the first message then terminates the connection\n\tif _, err := clientConn.Write([]byte(\"hello\\n\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := bufio.NewReader(clientConn).ReadString('\\n'); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tclientConn.Close()\n\n\t// wait for up to 30s for backend connection to be closed\n\tselect {\n\tcase err = <-done:\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\tcase <-time.After(30 * time.Second):\n\t\tt.Error(\"Server did not close connection\")\n\t}\n}\n\ntype testServer struct {\n\ttestpb.UnimplementedTestServiceServer\n\tresponsePayload []byte\n}\n\nfunc (s *testServer) EmptyCall(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {\n\treturn &testpb.Empty{}, nil\n}\n\nfunc (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {\n\treturn &testpb.SimpleResponse{\n\t\tPayload: &testpb.Payload{\n\t\t\tBody: s.responsePayload,\n\t\t},\n\t}, nil\n}\n\ntype relayWithGrpcServer struct {\n\tListener   net.Listener\n\tGrpcServer *grpc.Server\n\tRelay      *relay\n\tConn       *grpc.ClientConn\n\tCtx        context.Context\n}\n\nfunc (r *relayWithGrpcServer) mustStop(t *testing.T) {\n\tr.Listener.Close()\n\tr.GrpcServer.Stop()\n\tif err := r.Relay.stop(); err != nil {\n\t\tt.Fatal(\"failed to stop relay: \", err)\n\t}\n\tr.Conn.Close()\n}\n\nfunc mustStartRelayWithGrpcServer(t *testing.T, service testpb.TestServiceServer) (testpb.TestServiceClient, *relayWithGrpcServer) {\n\tt.Helper()\n\n\tresult := &relayWithGrpcServer{}\n\tvar err error\n\n\t// Setup gRPC test server.\n\tresult.Listener, err = net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to listen: %v\", err)\n\t}\n\tresult.GrpcServer = grpc.NewServer()\n\ttestpb.RegisterTestServiceServer(result.GrpcServer, service)\n\tgo result.GrpcServer.Serve(result.Listener)\n\n\t// Start relay client and server.\n\tbackendAddress := fmt.Sprintf(\"127.0.0.1:%d\", result.Listener.Addr().(*net.TCPAddr).Port)\n\tresult.Relay = &relay{}\n\tif err := result.Relay.start(backendAddress, \"--force_http2\"); err != nil {\n\t\tt.Fatal(\"failed to start relay: \", err)\n\t}\n\trelayAddress := \"127.0.0.1:\" + result.Relay.rsPort\n\n\t// Create gRPC client via relay.\n\tresult.Ctx = metadata.AppendToOutgoingContext(context.Background(), \"x-server-name\", \"remote1\")\n\tresult.Conn, err = grpc.DialContext(result.Ctx, relayAddress, grpc.WithInsecure())\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create client connection: %v\", err)\n\t}\n\n\treturn testpb.NewTestServiceClient(result.Conn), result\n}\n\n// TestGrpcRelaySimpleCallWorks launches a local http relay (client + server), connects a\n// grpc service as backend and issues a simple call.\nfunc TestGrpcRelaySimpleCallWorks(t *testing.T) {\n\tclient, relayAndServer := mustStartRelayWithGrpcServer(t, &testServer{})\n\tdefer relayAndServer.mustStop(t)\n\n\tif _, err := client.EmptyCall(relayAndServer.Ctx, &testpb.Empty{}); err != nil {\n\t\tif ec, ok := status.FromError(err); ok {\n\t\t\tif ec.Code() != codes.OK {\n\t\t\t\tt.Errorf(\"Wrong error code: got %d, expected %d\", ec.Code(), codes.OK)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// TestGrpcRelayChunkingOfLargeResponseWorks launches a local http relay\n// (client + server), connects a grpc service as backend and issues a call\n// with a response that has to be chunked.\nfunc TestGrpcRelayChunkingOfLargeResponseWorks(t *testing.T) {\n\t// Make responses from gRPC server larger than the default MaxChunkSize.\n\tpayload := make([]byte, client.DefaultClientConfig().MaxChunkSize*5)\n\tfor i := 0; i < len(payload); i++ {\n\t\tpayload[i] = byte(i) // Fill with non-zeroes\n\t}\n\ttestServer := &testServer{responsePayload: payload}\n\tclient, relayAndServer := mustStartRelayWithGrpcServer(t, testServer)\n\tdefer relayAndServer.mustStop(t)\n\n\tresponse, err := client.UnaryCall(relayAndServer.Ctx, &testpb.SimpleRequest{})\n\tif err != nil {\n\t\tif ec, ok := status.FromError(err); ok {\n\t\t\tif ec.Code() != codes.OK {\n\t\t\t\tt.Fatalf(\"Wrong error code: got %d, expected %d.\", ec.Code(), codes.OK)\n\t\t\t}\n\t\t}\n\t}\n\n\tif !bytes.Equal(response.Payload.Body, testServer.responsePayload) {\n\t\tt.Errorf(\"Received payload not equal to payload returned by server.\")\n\t}\n}\n\n// TestGrpcRelayErrorArePropagated launches a local http relay (client + server), connects a\n// grpc service as backend and issues a simple call.\nfunc TestGrpcRelayErrorArePropagated(t *testing.T) {\n\t// UnimplementedTestServiceServer returns Unimplemented for all RPCs.\n\tclient, relayAndServer := mustStartRelayWithGrpcServer(t, &testpb.UnimplementedTestServiceServer{})\n\tdefer relayAndServer.mustStop(t)\n\n\tif _, err := client.EmptyCall(relayAndServer.Ctx, &testpb.Empty{}); err != nil {\n\t\tif ec, ok := status.FromError(err); ok {\n\t\t\tif ec.Code() != codes.Unimplemented {\n\t\t\t\tt.Errorf(\"Wrong error code: got %d, expected %d\", ec.Code(), codes.Unimplemented)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/go/tests/relay-bench.sh",
    "content": "#!/bin/bash\n# Runs a local http server, a local relay server + client and compares direct \n# access to relayed access.\n\ntrap suite_cleanup INT\n\nbackend_server_port=8082\nbackend_server=\"http://localhost:${backend_server_port}\"\nrelay_server_port=8081\nrelay_server=\"http://localhost:${relay_server_port}\"\nserver_name=\"test\"\n\n# per suite\nfunction suite_init() {\n  sudo cpufreq-set -g performance\n  bazel build //src/go/cmd/http-relay-client:http-relay-client-bin //src/go/cmd/http-relay-server:http-relay-server-bin\n  which >/dev/null go-httpbin || die \"Please run: go install github.com/mccutchen/go-httpbin/v2/cmd/go-httpbin\"\n}\n\nfunction suite_cleanup() {\n  sudo cpufreq-set -g ondemand\n  test_cleanup\n}\n\n# per test\nfunction test_init() {\n  go-httpbin >/tmp/backend_server.log 2>&1 \\\n    -host 127.0.0.1 -port 8082 &\n  backend_server_pid=$!\n  bazel-bin/src/go/cmd/http-relay-server/http-relay-server-bin_/http-relay-server-bin >/tmp/relay_server.log 2>&1 \\\n    --port=${relay_server_port} &\n  relay_server_pid=$!\n  # ensure server is up\n  sleep 1s\n  bazel-bin/src/go/cmd/http-relay-client/http-relay-client-bin_/http-relay-client-bin  >/tmp/relay_client.log 2>&1 \\\n    --backend_address=localhost:${backend_server_port} --backend_scheme=http --relay_scheme=http --server_name=\"${server_name}\" \"$@\" &\n  relay_client_pid=$!\n}\n\nfunction test_cleanup() {\n  test -n \"$relay_client_pid\" && kill $relay_client_pid && unset relay_client_pid\n  test -n \"$relay_server_pid\" && kill $relay_server_pid && unset relay_server_pid\n  test -n \"$backend_server_pid\" && kill $backend_server_pid && unset backend_server_pid\n}\n\n# helper\nfunction die {\n  echo \"$1\" >&2\n  exit 1\n}\n\nfunction get_avg() {\n  awk -F',' '{sum+=$7} END {print sum/NR}' $1\n}\n\nfunction status() {\n  printf \"%s: direct=%9.7fs, relay=%9.7fs, slowdown=%9.7fs\\n\" $1 $2 $3 $(echo $3/$2 | bc -l)\n}\n\nfunction curltime() {\n    curl -w @- -o /dev/null -s \"$@\" <<'EOF'\n%{time_namelookup},%{time_connect},%{time_appconnect},%{time_pretransfer},%{time_redirect},%{time_starttransfer},%{time_total}\\n\nEOF\n}\n\n# benchmarks\nfunction run_test() {\n  test_name=\"$1\"\n  shift\n  echo \"==== $test_name : $@\"\n  local num_runs\n  num_runs=$1\n  shift\n  local req_path\n  req_path=\"$1\"\n  shift\n  test_init \"$@\"\n  \n  (for i in $(seq $num_runs); do curltime ${backend_server}/${req_path}; done) >/tmp/direct.seq.csv\n  direct=$(get_avg /tmp/direct.seq.csv)\n  (for i in $(seq $num_runs); do curltime ${relay_server}/${req_path} -H\"X-Server-Name: ${server_name}\"; done) >/tmp/relay.seq.csv\n  relay=$(get_avg /tmp/relay.seq.csv)\n  status \"seq\" ${direct} ${relay}\n\n\n  (for i in $(seq $num_runs); do curltime ${backend_server}/${req_path} & done) >/tmp/direct.par.csv; \n  sleep 1s\n  direct=$(get_avg /tmp/direct.par.csv)\n  (for i in $(seq $num_runs); do curltime ${relay_server}/${req_path} -H\"X-Server-Name: ${server_name}\" & done) >/tmp/relay.par.csv;\n  sleep 1s\n  relay=$(get_avg /tmp/relay.par.csv)\n  status \"par\" ${direct} ${relay}\n  \n  test_cleanup\n}\n\nfunction run() {\n  suite_init\n  local num_runs\n  num_runs=100\n  run_test \"   default_params\" $num_runs \"bytes/100000\"\n  run_test \"    more_requests\" $num_runs \"bytes/100000\" --max_idle_conns_per_host=100 --num_pending_requests=10\n  run_test \"    more_requests\" $num_runs \"bytes/100000\" --max_idle_conns_per_host=100 --num_pending_requests=50\n  suite_cleanup\n}\n\nif [[ -z \"$1\" ]]; then\n  run\nelse\n  # call arguments verbatim:\n  \"$@\"\nfi\n\n"
  },
  {
    "path": "src/go/tests/relay_test.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2019 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# This test only works in conjunction with a sim vm. E.g. from the top of the\n# repo run: (one time)\n#\n#     ./scripts/robot-sim.sh create \"${PROJECT:?}\" \"sim1\"\n#\n# Then to deploy and test the relay:\n#\n#   ./deploy.sh fast_push \"${PROJECT:?}\"\n#   sleep 30  # allow time for http-relay-server/client to update\n#   bazel test --test_env GCP_PROJECT_ID=\"${PROJECT:?}\" --test_env CLUSTER=\"sim1\" --test_env HOME=\"${HOME}\" --test_output=streamed --test_tag_filters=\"external\" //src/go/tests:relay_test\n#\n# Instead of `sleep 30` you can watch:\n#\n#   kubectl --context gke_${PROJECT:?}_${ZONE:?}_cloud-robotics -n app-k8s-relay get pods -w\n#   kubectl --context gke_${PROJECT:?}_${ZONE:?}_sim1 -n app-k8s-relay get pods -w\n#\n# Add -v7 to kc exec in the tests to get more details when debugging.\n\nCLUSTER=\"${CLUSTER:-test-robot}\"\nTEST_POD_NAME=\"busybox-sleep\"\nKC_DIR=$(mktemp -d -t kc-XXXXXXXXXX)\nexport KUBECONFIG=\"${KC_DIR}/test\"\ntouch \"${KUBECONFIG}\"\nexport KUBECACHE=\"${KC_DIR}/cache\"\nmkdir -p \"${KUBECACHE}\"\n\n# gcloud expects to be able to write to its config directly.\nCLOUDSDK_CONFIG=$(mktemp -d -t gcloud-XXXXXXXXXX)\nexport CLOUDSDK_CONFIG\ncp -a ~/.config/gcloud/* \"${CLOUDSDK_CONFIG}\"\n\nfunction kc() {\n  kubectl --cache-dir=\"${KUBECACHE}\" --context=\"${CLUSTER}\" \"$@\"\n}\n\nfunction setup() {\n  # configure kubectl to use relay for robot-sim vm (test-robot)\n  kubectl config set-credentials \"${GCP_PROJECT_ID}\" --exec-command=gke-gcloud-auth-plugin --exec-api-version=client.authentication.k8s.io/v1beta1\n  sed -i \"s/provideClusterInfo: false/provideClusterInfo: true/\" \"${KUBECONFIG}\"\n  kubectl config set-cluster \"${CLUSTER}\" --server=\"https://www.endpoints.${GCP_PROJECT_ID}.cloud.goog/apis/core.kubernetes-relay/client/${CLUSTER}\"\n  kubectl config set-context \"${CLUSTER}\" --cluster \"${CLUSTER}\" --namespace \"default\" --user \"$GCP_PROJECT_ID\"\n  echo \"Checking relay is working...\"\n  kubectl --context \"${CLUSTER}\" version || test_failed \"during setup, failed to reach the robot-sim VM\"\n\n  # delete test pod (if running)\n  if kc get pod \"${TEST_POD_NAME}\" -o name 2>/dev/null; then\n    kc delete pod --ignore-not-found \"${TEST_POD_NAME}\"\n    kc wait --for=delete pod/\"${TEST_POD_NAME}\" --timeout=60s\n  fi\n  # deploy a container with a shell that runs sleep\n  kc run \"${TEST_POD_NAME}\" --image=gcr.io/google-containers/busybox:1.27.2 --restart=Never -- /bin/sh -c \"trap : TERM INT; sleep 3600 & wait\"\n  kc wait --for=condition=Ready pod/\"${TEST_POD_NAME}\"\n}\n\nfunction teardown() {\n  # delete test pod (if running)\n  kc delete pod --ignore-not-found \"${TEST_POD_NAME}\" || /bin/true\n\n  rm -rf \"${KC_CFG_DIR}\" \"${CLOUDSDK_CONFIG}\"\n}\n\nfunction test_failed() {\n  echo \"TEST FAILED: $1\"\n  exit 1\n}\n\nfunction test_passed() {\n  echo \"TEST PASSED: $1\"\n}\n\nfunction test_relay_can_exec_to_shell() {\n  # exec command in shell-container through the relay\n  res=$(kc exec \"${TEST_POD_NAME}\" -- echo hello)\n  if [[ \"$res\" != \"hello\" ]]; then\n    test_failed \"echo command did not run, output was \\\"$res\\\", want \\\"hello\\\"\"\n  fi\n\n  test_passed \"echo command worked\"\n}\n\nfunction test_relay_handles_eof() {\n  # pipe commands from stdin through the relay\n  res=$({ echo \"echo foo\"; } | kc exec \"${TEST_POD_NAME}\" -i -- sh)\n  if [[ \"$res\" != \"foo\" ]]; then\n    test_failed \"echo command did not run, output was \\\"$res\\\"\"\n  fi\n\n  test_passed \"echo command worked\"\n}\n\nsetup\ntrap teardown EXIT\ntest_relay_can_exec_to_shell\ntest_relay_handles_eof\n\n"
  },
  {
    "path": "src/go.mod",
    "content": "module github.com/googlecloudrobotics/core/src\n\ngo 1.25.0\n\ntoolchain go1.25.4\n\nrequire (\n\tcloud.google.com/go v0.123.0 // indirect\n\tcontrib.go.opencensus.io/exporter/prometheus v0.4.2\n\tcontrib.go.opencensus.io/exporter/stackdriver v0.13.14\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver v1.5.0 // indirect\n\tgithub.com/Masterminds/sprig v2.22.0+incompatible // indirect\n\tgithub.com/cenkalti/backoff v2.2.1+incompatible\n\tgithub.com/cyphar/filepath-securejoin v0.2.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590\n\tgithub.com/getlantern/mockconn v0.0.0-20190403061815-a8ffa60494a6 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/golang/mock v1.7.0-rc.1\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/huandu/xstrings v1.3.3 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/motemen/go-loghttp v0.0.0-20170804080138-974ac5ceac27\n\tgithub.com/motemen/go-nuts v0.0.0-20220604134737-2658d0104f31 // indirect\n\tgithub.com/onsi/gomega v1.27.10\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/prometheus/client_golang v1.17.0\n\tgithub.com/prometheus/procfs v0.12.0 // indirect\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/spf13/pflag v1.0.10\n\tgo.opencensus.io v0.24.0\n\tgolang.org/x/crypto v0.46.0\n\tgolang.org/x/net v0.48.0\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgoogle.golang.org/api v0.256.0\n\tgoogle.golang.org/grpc v1.79.3\n\tgoogle.golang.org/protobuf v1.36.11\n\tgopkg.in/h2non/gock.v1 v1.1.2\n\tk8s.io/api v0.28.4\n\tk8s.io/apiextensions-apiserver v0.28.4\n\tk8s.io/apimachinery v0.28.4\n\tk8s.io/cli-runtime v0.28.4\n\tk8s.io/client-go v0.28.4\n\tk8s.io/helm v2.17.0+incompatible\n\tk8s.io/klog v1.0.0\n\tsigs.k8s.io/controller-runtime v0.16.3\n\tsigs.k8s.io/kind v0.17.0\n\tsigs.k8s.io/yaml v1.6.0\n)\n\nrequire (\n\tcloud.google.com/go/storage v1.59.2\n\tgithub.com/aws/aws-sdk-go v1.45.25 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/prometheus/statsd_exporter v0.22.8 // indirect\n\tgolang.org/x/oauth2 v0.34.0\n\tgoogle.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect\n)\n\nrequire (\n\tgithub.com/form3tech-oss/jwt-go v3.2.5+incompatible\n\tgithub.com/golang/glog v1.2.5\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/google/nftables v0.3.0\n\tgithub.com/googlecloudrobotics/ilog v0.0.0-20240112131211-2efd642f756e\n\tgithub.com/jaypipes/ghw v0.17.0\n\tk8s.io/klog/v2 v2.110.1\n)\n\nrequire (\n\tcel.dev/expr v0.25.1 // indirect\n\tcloud.google.com/go/auth v0.17.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.1.4 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect\n\tgithub.com/mdlayher/socket v0.5.0 // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect\n\tgithub.com/spiffe/go-spiffe/v2 v2.6.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.43.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.2 // indirect\n)\n\nrequire (\n\tcloud.google.com/go/compute/metadata v0.9.0\n\tcloud.google.com/go/iam v1.5.3 // indirect\n\tcloud.google.com/go/monitoring v1.24.2 // indirect\n\tcloud.google.com/go/trace v1.11.6 // indirect\n\tgithub.com/BurntSushi/toml v1.2.1 // indirect\n\tgithub.com/StackExchange/wmi v1.2.1 // indirect\n\tgithub.com/alessio/shellescape v1.4.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/census-instrumentation/opencensus-proto v0.4.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.11.0 // indirect\n\tgithub.com/evanphx/json-patch v5.7.0+incompatible // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.7.0 // indirect\n\tgithub.com/ghodss/yaml v1.0.0 // indirect\n\tgithub.com/go-errors/errors v1.4.2 // indirect\n\tgithub.com/go-kit/log v0.2.1 // indirect\n\tgithub.com/go-logfmt/logfmt v0.6.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/zapr v1.2.4 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.20.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.22.4 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/google/btree v1.1.2 // indirect\n\tgithub.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/google/safetext v0.0.0-20221026122733-23539d61753f // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect\n\tgithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect\n\tgithub.com/imdario/mergo v0.3.16 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jaypipes/pcidb v1.0.1\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-isatty v0.0.19 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/peterbourgon/diskv v2.0.1+incompatible // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.45.0 // indirect\n\tgithub.com/prometheus/prometheus v0.48.0 // indirect\n\tgithub.com/xlab/treeprint v1.2.0 // indirect\n\tgo.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.26.0 // indirect\n\tgolang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect\n\tgolang.org/x/term v0.38.0 // indirect\n\tgolang.org/x/text v0.32.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\thowett.net/plist v1.0.0 // indirect\n\tk8s.io/component-base v0.28.4 // indirect\n\tk8s.io/kube-openapi v0.0.0-20231129212854-f0671cc7e66a // indirect\n\tk8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/kustomize/api v0.15.0 // indirect\n\tsigs.k8s.io/kustomize/kyaml v0.15.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect\n)\n"
  },
  {
    "path": "src/go.sum",
    "content": "cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=\ncel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=\ncloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=\ncloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=\ncloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=\ncloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=\ncloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=\ncloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=\ncloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=\ncloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=\ncloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=\ncloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.59.2 h1:gmOAuG1opU8YvycMNpP+DvHfT9BfzzK5Cy+arP+Nocw=\ncloud.google.com/go/storage v1.59.2/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=\ncloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=\ncloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=\ncontrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg=\ncontrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ=\ncontrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4=\ncontrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=\ngithub.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=\ngithub.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=\ngithub.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=\ngithub.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=\ngithub.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=\ngithub.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=\ngithub.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4=\ngithub.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=\ngithub.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=\ngithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=\ngithub.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=\ngithub.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=\ngithub.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=\ngithub.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=\ngithub.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=\ngithub.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=\ngithub.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=\ngithub.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=\ngithub.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8=\ngithub.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590 h1:OhyiFx+yBN30O3IHrIq+9LAEhy6o7fin21wUQxF8NiE=\ngithub.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590/go.mod h1:rE/jidqqHHG9sjSxC24Gd5YCfZ1AT91C2wjJ28TAOfA=\ngithub.com/getlantern/mockconn v0.0.0-20190403061815-a8ffa60494a6 h1:+aO65ByJw74kV8vXqvkj49P5RtIqyUObyeRTIxMz218=\ngithub.com/getlantern/mockconn v0.0.0-20190403061815-a8ffa60494a6/go.mod h1:+F5GJ7qGpQ03DBtcOEyQpM30ix4BLswdaojecFtsdy8=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=\ngithub.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=\ngithub.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=\ngithub.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=\ngithub.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=\ngithub.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=\ngithub.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=\ngithub.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=\ngithub.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=\ngithub.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=\ngithub.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=\ngithub.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=\ngithub.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=\ngithub.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=\ngithub.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=\ngithub.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=\ngithub.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=\ngithub.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg=\ngithub.com/google/safetext v0.0.0-20221026122733-23539d61753f h1:03r+JaAB8/2z83KOOCZK95tslx6e41NZS4Tpt569MtY=\ngithub.com/google/safetext v0.0.0-20221026122733-23539d61753f/go.mod h1:mJNEy0r5YPHC7ChQffpOszlGB4L1iqjXWpIEKcFpr9s=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/googlecloudrobotics/ilog v0.0.0-20240112131211-2efd642f756e h1:lfnmC6SUHV/5QrqXElmZ0WgojfIccKVNtxDry4T3AS8=\ngithub.com/googlecloudrobotics/ilog v0.0.0-20240112131211-2efd642f756e/go.mod h1:t9Up/i5bPfkBc7lEE+p0+lcD0NDw2zTTr19x19D7720=\ngithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=\ngithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=\ngithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=\ngithub.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=\ngithub.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jaypipes/ghw v0.17.0 h1:EVLJeNcy5z6GK/Lqby0EhBpynZo+ayl8iJWY0kbEUJA=\ngithub.com/jaypipes/ghw v0.17.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=\ngithub.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=\ngithub.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=\ngithub.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=\ngithub.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=\ngithub.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=\ngithub.com/motemen/go-loghttp v0.0.0-20170804080138-974ac5ceac27 h1:uAI3rnOT1OSSY4PUtI/M1orb3q0ewkovwd3wr8xSno4=\ngithub.com/motemen/go-loghttp v0.0.0-20170804080138-974ac5ceac27/go.mod h1:6eu9CfGt5kfrMVgeu9MfB9PRUnpc47I+udLswiTszI8=\ngithub.com/motemen/go-nuts v0.0.0-20220604134737-2658d0104f31 h1:lQ+0Zt2gm+w5+9iaBWKdJXC/gMrWjHhNbw9ts/9rSZ4=\ngithub.com/motemen/go-nuts v0.0.0-20220604134737-2658d0104f31/go.mod h1:vkBO+XDNzovo+YLBpUod2SFvuWLObXlERnfj99RP3rU=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=\ngithub.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=\ngithub.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=\ngithub.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=\ngithub.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=\ngithub.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=\ngithub.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=\ngithub.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=\ngithub.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=\ngithub.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=\ngithub.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=\ngithub.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=\ngithub.com/prometheus/prometheus v0.48.0 h1:yrBloImGQ7je4h8M10ujGh4R6oxYQJQKlMuETwNskGk=\ngithub.com/prometheus/prometheus v0.48.0/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g=\ngithub.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI=\ngithub.com/prometheus/statsd_exporter v0.22.8 h1:Qo2D9ZzaQG+id9i5NYNGmbf1aa/KxKbB9aKfMS+Yib0=\ngithub.com/prometheus/statsd_exporter v0.22.8/go.mod h1:/DzwbTEaFTE0Ojz5PqcSk6+PFHOPWGxdXVr6yC8eFOM=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=\ngithub.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=\ngithub.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=\ngithub.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=\ngithub.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=\ngo.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=\ngo.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=\ngo.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=\ngo.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=\ngo.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=\ngo.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=\ngo.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=\ngo.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=\ngo.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=\ngo.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=\ngo.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=\ngo.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=\ngo.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=\ngo.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=\ngolang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=\ngolang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=\ngolang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=\ngoogle.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc=\ngoogle.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/evanphx/json-patch.v5 v5.6.0 h1:BMT6KIwBD9CaU91PJCZIe46bDmBWa9ynTQgJIOpfQBk=\ngopkg.in/evanphx/json-patch.v5 v5.6.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk=\ngopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=\ngopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhowett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=\nhowett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=\nk8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=\nk8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=\nk8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=\nk8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=\nk8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=\nk8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=\nk8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q=\nk8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k=\nk8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=\nk8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=\nk8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=\nk8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=\nk8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao=\nk8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=\nk8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=\nk8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=\nk8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=\nk8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=\nk8s.io/kube-openapi v0.0.0-20231129212854-f0671cc7e66a h1:ZeIPbyHHqahGIbeyLJJjAUhnxCKqXaDY+n89Ms8szyA=\nk8s.io/kube-openapi v0.0.0-20231129212854-f0671cc7e66a/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=\nk8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI=\nk8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=\nsigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/kind v0.17.0 h1:CScmGz/wX66puA06Gj8OZb76Wmk7JIjgWf5JDvY7msM=\nsigs.k8s.io/kind v0.17.0/go.mod h1:Qqp8AiwOlMZmJWs37Hgs31xcbiYXjtXlRBSftcnZXQk=\nsigs.k8s.io/kustomize/api v0.15.0 h1:6Ca88kEOBVotHDw+y2IsIMYtg9Pvv7MKpW9JMyF/OH4=\nsigs.k8s.io/kustomize/api v0.15.0/go.mod h1:p19kb+E14gN7zcIBR/nhByJDAfUa7N8mp6ZdH/mMXbg=\nsigs.k8s.io/kustomize/kyaml v0.15.0 h1:ynlLMAxDhrY9otSg5GYE2TcIz31XkGZ2Pkj7SdolD84=\nsigs.k8s.io/kustomize/kyaml v0.15.0/go.mod h1:+uMkBahdU1KNOj78Uta4rrXH+iH7wvg+nW7+GULvREA=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=\nsigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "src/gomod.sh",
    "content": "#!/usr/bin/env bash\n#\n# Copyright 2021 The Cloud Robotics Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script updates the Go dependencies in go.mod and go.sum and applies\n# changes to the WORKSPACE file.\nset -e\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\nexport GO111MODULE=on\n\ncd ${DIR}\ngo mod tidy -compat=1.17\nbazel run //:gazelle\n\necho \"updates done\"\n\n"
  },
  {
    "path": "src/proto/http-relay/BUILD.bazel",
    "content": "load(\"@com_google_protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\n# http relay api\n\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nproto_library(\n    name = \"http_over_rpc_proto\",\n    srcs = [\"http_over_rpc.proto\"],\n)\n\ngo_proto_library(\n    name = \"http_over_rpc_proto_go\",\n    importpath = \"github.com/googlecloudrobotics/core/src/proto/http-relay\",\n    proto = \":http_relay_proto\",\n)\n\nproto_library(\n    name = \"http_relay_proto\",\n    srcs = [\"http_over_rpc.proto\"],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"unused.go\"],\n    embed = [\":http_over_rpc_proto_go\"],\n    importpath = \"github.com/googlecloudrobotics/core/src/proto/http-relay\",\n)\n"
  },
  {
    "path": "src/proto/http-relay/http_over_rpc.proto",
    "content": "// Copyright 2019 The Cloud Robotics Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// These messages encapsulate an HTTP request. They're used by the Kubernetes\n// relay to encapsulate an HTTP request as a payload over an RPC channel.\nsyntax = \"proto2\";\n\npackage cloudrobotics.http_relay.v1alpha1;\n\noption go_package = \"github.com/googlecloudrobotics/core/src/proto/http-relay;http_relay\";\n\n\nmessage HttpHeader {\n  optional string name = 1;\n  optional string value = 2;\n}\n\nmessage HttpRequest {\n  optional string id = 1;\n  optional string method = 2;\n  optional string host = 6;\n  optional string url = 3;\n  repeated HttpHeader header = 4;\n  optional bytes body = 5;\n}\n\n// Each HttpRequest may generate a stream of multiple HTTP responses with the\n// same id. The first response in the stream must contain status_code and\n// header, and only the last response in the stream must have eof set to true.\n// It's legal to send just one message with the entire response.\nmessage HttpResponse {\n  optional string id = 4;\n  optional int32 status_code = 1;\n  repeated HttpHeader header = 2;\n  optional bytes body = 3;\n  optional bool eof = 5;\n  repeated HttpHeader trailer = 6;\n  optional int64 backend_duration_ms=7;\n}\n"
  },
  {
    "path": "src/proto/http-relay/unused.go",
    "content": "// package http_relay is generated by the proto compiler during the build\n// process. This dummy file exists to make the Golang toolchain happy.\n// We may need to replace it with a generated file to truly fix the build,\n// though.\n//\n//go:generate protoc --go_out=. --go_opt=paths=source_relative http_over_rpc.proto\npackage http_relay\n"
  },
  {
    "path": "third_party/BUILD",
    "content": ""
  },
  {
    "path": "third_party/BUILD.bazel",
    "content": "licenses([\"notice\"])\n\nexports_files(\n    glob([\"*.BUILD\"]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/README.md",
    "content": "# 3rd party deps\n\nThis directory contains bazel support for 3rd-party deps and vendored\ndependencies.\n\n## 3rd party helm charts\n\nWe store version pinned 3rd party helm charts here to achieve a hermetic build.\n\nTo update a chart, first setup helm:\n\n```shell\ncurl -s https://get.helm.sh/helm-v2.17.0-linux-amd64.tar.gz | tar xzf - -C ~/bin --strip-components=1 linux-amd64/helm\nhelm init --client-only\n```\n\nNext check available versions and fetch the specific version to store:\n\n```shell\nhelm search prometheus-community/prometheus-operator --version='>7.0.0' --versions\nhelm fetch prometheus-community/prometheus-operator --version=7.5.0\n```\n\n"
  },
  {
    "path": "third_party/akri/BUILD.bazel",
    "content": "# https://github.com/helm/charts/blob/master/LICENSE\n# Apache license\nlicenses([\"notice\"])\n\n# Helm chart for akri (version 0.12.9) included here was pulled from the\n# project-akri github repo and modified to be compatible with the Intrinsic\n# codebase. Future versions can be updated by running\n#   sh update-akri.sh\n\nexports_files(\n    glob([\n        \"*.yaml\",\n        \"*.tgz\",\n    ]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/akri/akri-configuration-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: configurations.akri.sh\nspec:\n  group: akri.sh\n  versions:\n    - name: v0\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                discoveryHandler: \n                  type: object\n                  properties:\n                    name:\n                      type: string\n                    discoveryDetails:\n                      type: string\n                    discoveryProperties:\n                      nullable: true\n                      type: array\n                      items: \n                        type: object\n                        required:\n                          - name\n                        properties:\n                          name:\n                            type: string\n                            pattern: \"^[_A-Za-z][_A-Za-z0-9]*$\"\n                          value:\n                            type: string\n                            nullable: true\n                          valueFrom:\n                            type: object\n                            properties:\n                              secretKeyRef:\n                                type: object\n                                required:\n                                  - name\n                                properties:\n                                  key:\n                                    type: string\n                                  name:\n                                    type: string\n                                  namespace:\n                                    type: string\n                                  optional:\n                                    type: boolean\n                              configMapKeyRef:\n                                type: object\n                                required:\n                                  - name\n                                properties:\n                                  key:\n                                    type: string\n                                  name:\n                                    type: string\n                                  namespace:\n                                    type: string\n                                  optional:\n                                    type: boolean\n                            oneOf:\n                              - properties:\n                                required: [\"secretKeyRef\"]\n                              - properties:\n                                required: [\"configMapKeyRef\"]\n                        oneOf:\n                          - properties:\n                            required: [\"value\"]\n                          - properties:\n                            required: [\"valueFrom\"]\n                capacity:\n                  type: integer\n                brokerSpec: \n                  type: object\n                  properties: \n                    brokerJobSpec: \n                      x-kubernetes-preserve-unknown-fields: true\n                      type: object\n                      nullable: true\n                    brokerPodSpec: \n                      x-kubernetes-preserve-unknown-fields: true\n                      type: object\n                      nullable: true\n                instanceServiceSpec: \n                  x-kubernetes-preserve-unknown-fields: true\n                  type: object\n                  nullable: true\n                configurationServiceSpec: \n                  x-kubernetes-preserve-unknown-fields: true\n                  type: object\n                  nullable: true\n                brokerProperties: \n                  additionalProperties:\n                    type: string\n                  type: object\n      additionalPrinterColumns:\n      - name: Capacity\n        type: string\n        description: The capacity for each Instance discovered\n        jsonPath: .spec.capacity\n      - name: Age\n        type: date\n        jsonPath: .metadata.creationTimestamp\n  scope: Namespaced\n  names:\n    plural: configurations\n    singular: configuration\n    kind: Configuration\n    shortNames:\n      - akric\n"
  },
  {
    "path": "third_party/akri/akri-instance-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: instances.akri.sh\nspec:\n  group: akri.sh\n  versions:\n    - name: v0\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                configurationName:\n                  type: string\n                brokerProperties:\n                  additionalProperties:\n                    type: string\n                  type: object\n                shared:\n                  type: boolean\n                nodes:\n                  type: array\n                  items:\n                    type: string\n                deviceUsage: # map<string, string>\n                  additionalProperties:\n                    type: string\n                  type: object\n      additionalPrinterColumns:\n      - name: Config\n        type: string\n        description: The Configuration this Instance belongs to\n        jsonPath: .spec.configurationName\n      - name: Shared\n        type: boolean\n        description: Describes whether this Instance is shared\n        jsonPath: .spec.shared\n      - name: Nodes\n        type: string\n        description: Nodes that expose this Instance\n        jsonPath: .spec.nodes\n      - name: Age\n        type: date\n        jsonPath: .metadata.creationTimestamp\n  scope: Namespaced\n  names:\n    plural: instances\n    singular: instance\n    kind: Instance\n    shortNames:\n    - akrii\n"
  },
  {
    "path": "third_party/akri/update-akri.sh",
    "content": "#!/bin/bash\n\nVERSION=0.12.9\n\n# fetch the latest version\nhelm repo add akri-helm-charts https://project-akri.github.io/akri/ \nhelm repo update akri-helm-charts\nhelm pull akri-helm-charts/akri --version=\"${VERSION}\"\n\n# Update crds\ntar xzf \"akri-${VERSION}.tgz\" --strip-components=2 akri/crds/\n# Strip template comments\nsed -i 's/#.*$//' akri-configuration-crd.yaml\n"
  },
  {
    "path": "third_party/app_crd.BUILD",
    "content": "# Kubernetes Applications CRD\n\npackage(\n    default_visibility = [\"//visibility:public\"],\n)\n\nlicenses([\"permissive\"])  # Apache 2.0\n\nfilegroup(\n    name = \"app_crd\",\n    srcs = [\n        \"config/crd/bases/app.k8s.io_applications.yaml\",\n    ],\n)\n"
  },
  {
    "path": "third_party/cert-manager/BUILD.bazel",
    "content": "# https://github.com/jetstack/cert-manager/blob/master/LICENSE\n# Apache 2.0 license\nlicenses([\"notice\"])\n\n# files downloaded by running:\n# helm repo add jetstack https://charts.jetstack.io --force-update\n# cert_manager_version=\"v1.16.3\"\n# helm pull jetstack/cert-manager --version ${cert_manager_version} -d third_party/cert-manager/\n\nexports_files(\n    glob([\n        \"*.tgz\",\n    ]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/cert-manager-google-cas-issuer/BUILD.bazel",
    "content": "# https://github.com/jetstack/google-cas-issuer/blob/main/LICENSE.txt\n# Apache 2.0 license\nlicenses([\"notice\"])\n\n# files downloaded by running:\n# cert_manager_version=\"v0.6.2\"\n# curl -o third_party/cert-manager/cert-manager-${cert_manager_version}.tgz https://charts.jetstack.io/charts/cert-manager-google-cas-issuer-${cert_manager_version}.tgz\n\n# alternatively, files can be downloaded using helm\n# cert_manager_version=\"v0.6.2\"\n# helm repo add cert-manager https://charts.jetstack.io\n# helm pull cert-manager/cert-manager-google-cas-issuer --version ${cert_manager_version}\n\nexports_files(\n    glob([\n        \"*.tgz\",\n    ]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/fluentd_gcp_addon/BUILD.bazel",
    "content": "# Description:\n#   Stackdriver Logging Agent is a DaemonSet which spawns a pod on each\n#   node that reads logs, generated by kubelet, container runtime and\n#   containers and sends them to the Stackdriver. When logs are exported\n#   to the Stackdriver, they can be searched, viewed, and analyzed.\n#\n# See:\n#   https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-gcp\n#   Last update: df908c3aad70be495b358ab6d8e62ec4b1ca0726\n#\n# In order to update them run:\n#\n#   THIS_DIR=$PWD\n#   cd <some_tmp_dir>\n#   git clone https://github.com/kubernetes/kubernetes.git\n#   cd kubernetes\n#   (source \"cluster/gce/gci/configure-helper.sh\" && setup-fluentd cluster/addons)\n#   for f in cluster/addons/fluentd-gcp/fluentd-gcp-{configmap,ds}.yaml; do \\\n#     grep -v \"namespace: kube-system\" $f >${THIS_DIR}/third_party/fluentd_gcp_addon/$(basename $f); \\\n#   done\n#\n# and apply changes as noted in each file.\n#\n# NOTE: The configmap has been amended to support multi line log entries\n# (search for \"BEGIN modifications\" in fluentd-gcp-configmap.yaml).\n\nlicenses([\"permissive\"])  # Apache 2.0\n\nfilegroup(\n    name = \"fluentd_gcp_addon\",\n    srcs = [\n        \"fluentd-gcp-configmap.yaml\",\n        \"fluentd-gcp-ds.yaml\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/fluentd_gcp_addon/fluentd-gcp-configmap.yaml",
    "content": "# Copied from https://github.com/kubernetes/kubernetes/\n# - removed:\n#   - addonmanager.kubernetes.io/mode label\n#\n# License: Apache 2.0\n# https://github.com/kubernetes/kubernetes/blob/master/LICENSE\n\n# This ConfigMap is used to ingest logs against new resources like\n# \"k8s_container\" and \"k8s_node\" when $LOGGING_STACKDRIVER_RESOURCE_TYPES is set\n# to \"new\".\n# When $LOGGING_STACKDRIVER_RESOURCE_TYPES is set to \"old\", the ConfigMap in\n# fluentd-gcp-configmap-old.yaml will be used for ingesting logs against old\n# resources like \"gke_container\" and \"gce_instance\".\nkind: ConfigMap\napiVersion: v1\ndata:\n  containers.input.conf: |-\n    # This configuration file for Fluentd is used\n    # to watch changes to Docker log files that live in the\n    # directory /var/lib/docker/containers/ and are symbolically\n    # linked to from the /var/log/containers directory using names that capture the\n    # pod name and container name. These logs are then submitted to\n    # Google Cloud Logging which assumes the installation of the cloud-logging plug-in.\n    #\n    # Example\n    # =======\n    # A line in the Docker log file might look like this JSON:\n    #\n    # {\"log\":\"2014/09/25 21:15:03 Got request with path wombat\\\\n\",\n    #  \"stream\":\"stderr\",\n    #   \"time\":\"2014-09-25T21:15:03.499185026Z\"}\n    #\n    # The original tag is derived from the log file's location.\n    # For example a Docker container's logs might be in the directory:\n    #  /var/lib/docker/containers/997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b\n    # and in the file:\n    #  997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b-json.log\n    # where 997599971ee6... is the Docker ID of the running container.\n    # The Kubernetes kubelet makes a symbolic link to this file on the host\n    # machine in the /var/log/containers directory which includes the pod name,\n    # the namespace name and the Kubernetes container name:\n    #    synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log\n    #    ->\n    #    /var/lib/docker/containers/997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b/997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b-json.log\n    # The /var/log directory on the host is mapped to the /var/log directory in the container\n    # running this instance of Fluentd and we end up collecting the file:\n    #   /var/log/containers/synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log\n    # This results in the tag:\n    #  var.log.containers.synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log\n    # where 'synthetic-logger-0.25lps-pod' is the pod name, 'default' is the\n    # namespace name, 'synth-lgr' is the container name and '997599971ee6..' is\n    # the container ID.\n    # The record reformer is used to extract pod_name, namespace_name and\n    # container_name from the tag and set them in a local_resource_id in the\n    # format of:\n    # 'k8s_container.<NAMESPACE_NAME>.<POD_NAME>.<CONTAINER_NAME>'.\n    # The reformer also changes the tags to 'stderr' or 'stdout' based on the\n    # value of 'stream'.\n    # local_resource_id is later used by google_cloud plugin to determine the\n    # monitored resource to ingest logs against.\n\n    # Json Log Example:\n    # {\"log\":\"[info:2016-02-16T16:04:05.930-08:00] Some log text here\\n\",\"stream\":\"stdout\",\"time\":\"2016-02-17T00:04:05.931087621Z\"}\n    # CRI Log Example:\n    # 2016-02-17T00:04:05.931087621Z stdout F [info:2016-02-16T16:04:05.930-08:00] Some log text here\n    <source>\n      @type tail\n      path /var/log/containers/*.log\n      pos_file /var/log/gcp-containers.log.pos\n      # Tags at this point are in the format of:\n      # reform.var.log.containers.<POD_NAME>_<NAMESPACE_NAME>_<CONTAINER_NAME>-<CONTAINER_ID>.log\n      tag reform.*\n      read_from_head true\n      <parse>\n        @type multi_format\n        <pattern>\n          format json\n          time_key time\n          time_format %Y-%m-%dT%H:%M:%S.%NZ\n        </pattern>\n        <pattern>\n          format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/\n          time_format %Y-%m-%dT%H:%M:%S.%N%:z\n        </pattern>\n      </parse>\n    </source>\n\n    # Concatenate multi line log entries.\n    # We search for the expected prefix using the same regex as the parser below\n    # and group all consecutive lines until the prefix is encountered again.\n    <filter reform.**>\n      @type concat\n      key log\n      multiline_start_regexp /^\\w\\d{4} [^\\s]*\\s+\\d+\\s+[^ \\]]+\\]/\n      flush_interval 1\n      timeout_label @OUTPUT\n      separator \"\"\n    </filter>\n  system.input.conf: |-\n    # Example:\n    #  <86>1 2021-03-16T08:32:50.502885+01:00 my-hostname sudo - - -  pam_unix(sudo:session): session opened for user root(uid=0) by admin(uid=1000)\n    <source>\n      @type tail\n      format syslog\n      path /var/log/syslog.fluentd\n      pos_file /var/log/syslog.fluentd.pos\n      message_format rfc5424\n      with_priority true\n      tag syslog\n    </source>\n\n    # Examples:\n    # time=\"2016-02-04T06:51:03.053580605Z\" level=info msg=\"GET /containers/json\"\n    # time=\"2016-02-04T07:53:57.505612354Z\" level=error msg=\"HTTP Error\" err=\"No such image: -f\" statusCode=404\n    # TODO(random-liu): Remove this after cri container runtime rolls out.\n    <source>\n      @type tail\n      format /^time=\"(?<time>[^)]*)\" level=(?<severity>[^ ]*) msg=\"(?<message>[^\"]*)\"( err=\"(?<error>[^\"]*)\")?( statusCode=($<status_code>\\d+))?/\n      path /var/log/docker.log\n      pos_file /var/log/gcp-docker.log.pos\n      tag docker\n    </source>\n\n    # Example:\n    # 2016/02/04 06:52:38 filePurge: successfully removed file /var/etcd/data/member/wal/00000000000006d0-00000000010a23d1.wal\n    <source>\n      @type tail\n      # Not parsing this, because it doesn't have anything particularly useful to\n      # parse out of it (like severities).\n      format none\n      path /var/log/etcd.log\n      pos_file /var/log/gcp-etcd.log.pos\n      tag etcd\n    </source>\n\n    # Multi-line parsing is required for all the kube logs because very large log\n    # statements, such as those that include entire object bodies, get split into\n    # multiple lines by glog.\n\n    # Example:\n    # I0204 07:32:30.020537    3368 server.go:1048] POST /stats/container/: (13.972191ms) 200 [[Go-http-client/1.1] 10.244.1.3:40537]\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/kubelet.log\n      pos_file /var/log/gcp-kubelet.log.pos\n      tag kubelet\n    </source>\n\n    # Example:\n    # I1118 21:26:53.975789       6 proxier.go:1096] Port \"nodePort for kube-system/default-http-backend:http\" (:31429/tcp) was open before and is still needed\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/kube-proxy.log\n      pos_file /var/log/gcp-kube-proxy.log.pos\n      tag kube-proxy\n    </source>\n\n    # Example:\n    # I0204 07:00:19.604280       5 handlers.go:131] GET /api/v1/nodes: (1.624207ms) 200 [[kube-controller-manager/v1.1.3 (linux/amd64) kubernetes/6a81b50] 127.0.0.1:38266]\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/kube-apiserver.log\n      pos_file /var/log/gcp-kube-apiserver.log.pos\n      tag kube-apiserver\n    </source>\n\n    # Example:\n    # I0204 06:55:31.872680       5 servicecontroller.go:277] LB already exists and doesn't need update for service kube-system/kube-ui\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/kube-controller-manager.log\n      pos_file /var/log/gcp-kube-controller-manager.log.pos\n      tag kube-controller-manager\n    </source>\n\n    # Example:\n    # W0204 06:49:18.239674       7 reflector.go:245] pkg/scheduler/factory/factory.go:193: watch of *api.Service ended with: 401: The event in requested index is outdated and cleared (the requested history has been cleared [2578313/2577886]) [2579312]\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/kube-scheduler.log\n      pos_file /var/log/gcp-kube-scheduler.log.pos\n      tag kube-scheduler\n    </source>\n\n    # Example:\n    # I0603 15:31:05.793605       6 cluster_manager.go:230] Reading config from path /etc/gce.conf\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/glbc.log\n      pos_file /var/log/gcp-glbc.log.pos\n      tag glbc\n    </source>\n\n    # Example:\n    # I0603 15:31:05.793605       6 cluster_manager.go:230] Reading config from path /etc/gce.conf\n    <source>\n      @type tail\n      format multiline\n      multiline_flush_interval 5s\n      format_firstline /^\\w\\d{4}/\n      format1 /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<message>.*)/\n      time_format %m%d %H:%M:%S.%N\n      path /var/log/cluster-autoscaler.log\n      pos_file /var/log/gcp-cluster-autoscaler.log.pos\n      tag cluster-autoscaler\n    </source>\n\n    # Logs from systemd-journal for interesting services.\n    # TODO(random-liu): Keep this for compatibility, remove this after\n    # cri container runtime rolls out.\n    <source>\n      @type systemd\n      filters [{ \"_SYSTEMD_UNIT\": \"docker.service\" }]\n      pos_file /var/log/gcp-journald-docker.pos\n      read_from_head true\n      tag docker\n    </source>\n\n    <source>\n      @type systemd\n      filters [{ \"_SYSTEMD_UNIT\": \"{{ fluentd_container_runtime_service }}.service\" }]\n      pos_file /var/log/gcp-journald-container-runtime.pos\n      read_from_head true\n      tag container-runtime\n    </source>\n\n    <source>\n      @type systemd\n      filters [{ \"_SYSTEMD_UNIT\": \"kubelet.service\" }]\n      pos_file /var/log/gcp-journald-kubelet.pos\n      read_from_head true\n      tag kubelet\n    </source>\n\n    <source>\n      @type systemd\n      filters [{ \"_SYSTEMD_UNIT\": \"node-problem-detector.service\" }]\n      pos_file /var/log/gcp-journald-node-problem-detector.pos\n      read_from_head true\n      tag node-problem-detector\n    </source>\n\n    # BEGIN_NODE_JOURNAL\n    # Whether to include node-journal or not is determined when starting the\n    # cluster. It is not changed when the cluster is already running.\n    <source>\n      @type systemd\n      pos_file /var/log/gcp-journald.pos\n      read_from_head true\n      tag node-journal\n    </source>\n\n    <filter node-journal>\n      @type grep\n      <exclude>\n        key _SYSTEMD_UNIT\n        pattern ^(docker|{{ fluentd_container_runtime_service }}|kubelet|node-problem-detector)\\.service$\n      </exclude>\n    </filter>\n    # END_NODE_JOURNAL\n  monitoring.conf: |-\n    # This source is used to acquire approximate process start timestamp,\n    # which purpose is explained before the corresponding output plugin.\n    <source>\n      @type exec\n      command /bin/sh -c 'date +%s'\n      tag process_start\n      time_format %Y-%m-%d %H:%M:%S\n      keys process_start_timestamp\n    </source>\n\n    # This filter is used to convert process start timestamp to integer\n    # value for correct ingestion in the prometheus output plugin.\n    <filter process_start>\n      @type record_transformer\n      enable_ruby true\n      auto_typecast true\n      <record>\n        process_start_timestamp ${record[\"process_start_timestamp\"].to_i}\n      </record>\n    </filter>\nmetadata:\n  name: fluentd-gcp-config-v1.2.5\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: fluentd-gcp-main-config\ndata:\n  google-fluentd.conf: |-\n    @include config.d/*.conf\n    <source>\n      @type prometheus\n      port 24231\n    </source>\n    <source>\n      @type prometheus_monitor\n    </source>\n\n    # This match is placed before the all-matching output to provide metric\n    # exporter with a process start timestamp for correct exporting of\n    # cumulative metrics to Stackdriver.\n    <match process_start>\n      @type prometheus\n\n      <metric>\n        type gauge\n        name process_start_time_seconds\n        desc Timestamp of the process start in seconds\n        key process_start_timestamp\n      </metric>\n    </match>\n\n    # Unify output of regular and \"timed out\" entries from the concat plugin.\n    # See https://github.com/fluent-plugins-nursery/fluent-plugin-concat/commit/5802235e71f6b31d741aa1205e710c08dee415cf.\n    # Ideally we would remove the @OUTPUT label from the timed out entries\n    # to avoid nesting the entire output logic under \"<label @OUTPUT>\" but\n    # fluentd does not seem to provide a way to do that.\n    <match **>\n      @type relabel\n      @label @OUTPUT\n    </match>\n\n    <label @OUTPUT>\n      <filter reform.**>\n        @type parser\n        format /^(?<severity>\\w)(?<time>\\d{4} [^\\s]*)\\s+(?<pid>\\d+)\\s+(?<source>[^ \\]]+)\\] (?<log>(.|\\n)*.)/\n        reserve_data true\n        suppress_parse_error_log true\n        emit_invalid_record_to_error false\n        key_name log\n      </filter>\n\n      <match reform.**>\n        @type record_reformer\n        enable_ruby true\n        <record>\n          # Extract local_resource_id from tag for 'k8s_container' monitored\n          # resource. The format is:\n          # 'k8s_container.<namespace_name>.<pod_name>.<container_name>'.\n          \"logging.googleapis.com/local_resource_id\" ${\"k8s_container.#{tag_suffix[4].rpartition('.')[0].split('_')[1]}.#{tag_suffix[4].rpartition('.')[0].split('_')[0]}.#{tag_suffix[4].rpartition('.')[0].split('_')[2].rpartition('-')[0]}\"}\n          # Rename the field 'log' to a more generic field 'message'. This way the\n          # fluent-plugin-google-cloud knows to flatten the field as textPayload\n          # instead of jsonPayload after extracting 'time', 'severity' and\n          # 'stream' from the record.\n          message ${record['log']}\n          # If 'severity' is not set, assume stderr is ERROR and stdout is INFO.\n          severity ${record['severity'] || if record['stream'] == 'stderr' then 'ERROR' else 'INFO' end}\n        </record>\n        tag ${if record['stream'] == 'stderr' then 'raw.stderr' else 'raw.stdout' end}\n        remove_keys stream,log\n      </match>\n\n      # Detect exceptions in the log output and forward them as one log entry.\n      <match {raw.stderr,raw.stdout}>\n        @type detect_exceptions\n\n        remove_tag_prefix raw\n        message message\n        stream \"logging.googleapis.com/local_resource_id\"\n        multiline_flush_interval 5\n        max_bytes 500000\n        max_lines 1000\n      </match>\n\n      # This filter allows to count the number of log entries read by fluentd\n      # before they are processed by the output plugin. This in turn allows to\n      # monitor the number of log entries that were read but never sent, e.g.\n      # because of liveness probe removing buffer.\n      <filter **>\n        @type prometheus\n        <metric>\n          type counter\n          name logging_entry_count\n          desc Total number of log entries generated by either application containers or system components\n        </metric>\n      </filter>\n\n      # This section is exclusive for k8s_container logs. Those come with\n      # 'stderr'/'stdout' tags.\n      # TODO(instrumentation): Reconsider this workaround later.\n      # Trim the entries which exceed slightly less than 100KB, to avoid\n      # dropping them. It is a necessity, because Stackdriver only supports\n      # entries that are up to 100KB in size.\n      <filter {stderr,stdout}>\n        @type record_transformer\n        enable_ruby true\n        <record>\n          message ${record['message'].length > 100000 ? \"[Trimmed]#{record['message'][0..100000]}...\" : record['message']}\n        </record>\n      </filter>\n\n      # Do not collect fluentd's own logs to avoid infinite loops.\n      <match fluent.**>\n        @type null\n      </match>\n\n      # Add a unique insertId to each log entry that doesn't already have it.\n      # This helps guarantee the order and prevent log duplication.\n      <filter **>\n        @type add_insert_ids\n      </filter>\n\n      # This section is exclusive for k8s_container logs. These logs come with\n      # 'stderr'/'stdout' tags.\n      # We use a separate output stanza for 'k8s_node' logs with a smaller buffer\n      # because node logs are less important than user's container logs.\n      <match {stderr,stdout}>\n        @type google_cloud\n\n        # Try to detect JSON formatted log entries.\n        detect_json true\n        # Collect metrics in Prometheus registry about plugin activity.\n        enable_monitoring true\n        monitoring_type prometheus\n        # Allow log entries from multiple containers to be sent in the same request.\n        split_logs_by_tag false\n        # Set the buffer type to file to improve the reliability and reduce the memory consumption\n        buffer_type file\n        buffer_path /var/log/fluentd-buffers/kubernetes.containers.buffer\n        # Set queue_full action to block because we want to pause gracefully\n        # in case of the off-the-limits load instead of throwing an exception\n        buffer_queue_full_action block\n        # Set the chunk limit conservatively to avoid exceeding the recommended\n        # chunk size of 5MB per write request.\n        buffer_chunk_limit 512k\n        # Length limit of chunk queue. Together with `buffer_chunk_limit` affects disk space used.\n        buffer_queue_limit 64\n        # Never wait more than 5 seconds before flushing logs in the non-error case.\n        flush_interval 5s\n        # Never wait longer than 30 seconds between retries.\n        max_retry_wait 30\n        # Disable the limit on the number of retries (retry forever).\n        disable_retry_limit\n        # Use multiple threads for processing.\n        num_threads 8\n        use_grpc true\n        # Skip timestamp adjustment as this is in a controlled environment with\n        # known timestamp format. This helps with CPU usage.\n        adjust_invalid_timestamps false\n      </match>\n\n      # Attach local_resource_id for 'k8s_node' monitored resource.\n      <filter **>\n        @type record_transformer\n        enable_ruby true\n        <record>\n          \"logging.googleapis.com/local_resource_id\" ${\"k8s_node.#{ENV['NODE_NAME']}\"}\n        </record>\n      </filter>\n\n      # This section is exclusive for 'k8s_node' logs. These logs come with tags\n      # that are neither 'stderr' or 'stdout'.\n      # We use a separate output stanza for 'k8s_container' logs with a larger\n      # buffer because user's container logs are more important than node logs.\n      <match **>\n        @type google_cloud\n\n        detect_json true\n        enable_monitoring true\n        monitoring_type prometheus\n        # Allow entries from multiple system logs to be sent in the same request.\n        split_logs_by_tag false\n        detect_subservice false\n        buffer_type file\n        buffer_path /var/log/fluentd-buffers/kubernetes.system.buffer\n        buffer_queue_full_action block\n        buffer_chunk_limit 512k\n        buffer_queue_limit 64\n        flush_interval 5s\n        max_retry_wait 30\n        disable_retry_limit\n        num_threads 8\n        use_grpc true\n        # Skip timestamp adjustment as this is in a controlled environment with\n        # known timestamp format. This helps with CPU usage.\n        adjust_invalid_timestamps false\n      </match>\n    </label>\n"
  },
  {
    "path": "third_party/fluentd_gcp_addon/fluentd-gcp-ds.yaml",
    "content": "# Copied from https://github.com/kubernetes/kubernetes/\n# - removed:\n#   - addonmanager.kubernetes.io/mode, kubernetes.io/cluster-service label\n#   - serviceAccountName\n#   - liveness probe\n#   - scheduling annotations\n#   - priorityClassName\n#   - nodeSelectors\n#   - tolerations\n#   - terminationGracePeriodSeconds\n#   - NODE_NAME, STACKDRIVER_METADATA_AGENT_URL env\n#   - hostNetwork: true (otherwise it won't find the metadata server)\n# - modified the path to local libs (/usr/lib) because it's different on MIR\n# - set the fluentd-gcp-config volume mode to 420\n# - added a taint toleration so it runs on all nodes\n#\n# For release notes check:\n# https://github.com/GoogleCloudPlatform/google-fluentd/releases\n#\n# License: Apache 2.0\n# https://github.com/kubernetes/kubernetes/blob/master/LICENSE\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: fluentd-gcp-v3.2.0\n  labels:\n    k8s-app: fluentd-gcp\n    version: v3.2.0\nspec:\n  selector:\n    matchLabels:\n      k8s-app: fluentd-gcp\n      version: v3.2.0\n  updateStrategy:\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: fluentd-gcp\n        version: v3.2.0\n    spec:\n      dnsPolicy: Default\n      containers:\n      - name: fluentd-gcp\n        image: gcr.io/stackdriver-agents/stackdriver-logging-agent:1.9.5\n        args:\n          - -q\n        volumeMounts:\n        - name: varlog\n          mountPath: /var/log\n        - name: varlibdockercontainers\n          mountPath: /var/lib/docker/containers\n          readOnly: true\n        - mountPath: /host/lib\n          name: libsystemddir\n          readOnly: true\n        - name: config-volume\n          mountPath: /etc/google-fluentd/config.d\n        - name: default-config-volume\n          mountPath: /etc/google-fluentd/google-fluentd.conf\n          subPath: google-fluentd.conf\n      volumes:\n      - name: varlog\n        hostPath:\n          path: /var/log\n      - name: varlibdockercontainers\n        hostPath:\n          path: /var/lib/docker/containers\n      - name: libsystemddir\n        hostPath:\n          path: /usr/lib\n      - name: config-volume\n        configMap:\n          name: fluentd-gcp-config-v1.2.5\n          defaultMode: 420\n      - name: default-config-volume\n        configMap:\n          name: fluentd-gcp-main-config\n          defaultMode: 420\n      tolerations:\n      - operator: \"Exists\"\n        effect: \"NoSchedule\"\n\n"
  },
  {
    "path": "third_party/helm2/BUILD.bazel",
    "content": "exports_files([\"helm\"])\n"
  },
  {
    "path": "third_party/helm3/BUILD.bazel",
    "content": "exports_files([\"helm\"])\n"
  },
  {
    "path": "third_party/ingress-nginx.BUILD",
    "content": "# Description:\n#   grafana dashboards\n\nlicenses([\"notice\"])  #  Apache-2.0\n\n\nfilegroup(\n    name = \"ingress-nginx-dashboards\",\n    srcs = glob([\"deploy/grafana/dashboards/*.json\"]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/kube-prometheus-stack/00-crds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: probes.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: Probe\n    listKind: ProbeList\n    plural: probes\n    shortNames:\n    - prb\n    singular: probe\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Probe defines monitoring for a set of static targets or ingresses.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Specification of desired Ingress selection for target discovery\n              by Prometheus.\n            properties:\n              authorization:\n                description: Authorization section for this endpoint\n                properties:\n                  credentials:\n                    description: Selects a key of a Secret in the namespace that contains\n                      the credentials for authentication.\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  type:\n                    description: \"Defines the authentication type. The value is case-insensitive.\n                      \\n \\\"Basic\\\" is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                    type: string\n                type: object\n              basicAuth:\n                description: 'BasicAuth allow an endpoint to authenticate over basic\n                  authentication. More info: https://prometheus.io/docs/operating/configuration/#endpoint'\n                properties:\n                  password:\n                    description: '`password` specifies a key of a Secret containing\n                      the password for authentication.'\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  username:\n                    description: '`username` specifies a key of a Secret containing\n                      the username for authentication.'\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                type: object\n              bearerTokenSecret:\n                description: Secret to mount to read bearer token for scraping targets.\n                  The secret needs to be in the same namespace as the probe and accessible\n                  by the Prometheus Operator.\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              interval:\n                description: Interval at which targets are probed using the configured\n                  prober. If not specified Prometheus' global scrape interval is used.\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              jobName:\n                description: The job name assigned to scraped metrics by default.\n                type: string\n              keepDroppedTargets:\n                description: \"Per-scrape limit on the number of targets dropped by\n                  relabeling that will be kept in memory. 0 means no limit. \\n It\n                  requires Prometheus >= v2.47.0.\"\n                format: int64\n                type: integer\n              labelLimit:\n                description: Per-scrape limit on number of labels that will be accepted\n                  for a sample. Only valid in Prometheus versions 2.27.0 and newer.\n                format: int64\n                type: integer\n              labelNameLengthLimit:\n                description: Per-scrape limit on length of labels name that will be\n                  accepted for a sample. Only valid in Prometheus versions 2.27.0\n                  and newer.\n                format: int64\n                type: integer\n              labelValueLengthLimit:\n                description: Per-scrape limit on length of labels value that will\n                  be accepted for a sample. Only valid in Prometheus versions 2.27.0\n                  and newer.\n                format: int64\n                type: integer\n              metricRelabelings:\n                description: MetricRelabelConfigs to apply to samples before ingestion.\n                items:\n                  description: \"RelabelConfig allows dynamic rewriting of the label\n                    set for targets, alerts, scraped samples and remote write samples.\n                    \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                  properties:\n                    action:\n                      default: replace\n                      description: \"Action to perform based on the regex matching.\n                        \\n `Uppercase` and `Lowercase` actions require Prometheus\n                        >= v2.36.0. `DropEqual` and `KeepEqual` actions require Prometheus\n                        >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                      enum:\n                      - replace\n                      - Replace\n                      - keep\n                      - Keep\n                      - drop\n                      - Drop\n                      - hashmod\n                      - HashMod\n                      - labelmap\n                      - LabelMap\n                      - labeldrop\n                      - LabelDrop\n                      - labelkeep\n                      - LabelKeep\n                      - lowercase\n                      - Lowercase\n                      - uppercase\n                      - Uppercase\n                      - keepequal\n                      - KeepEqual\n                      - dropequal\n                      - DropEqual\n                      type: string\n                    modulus:\n                      description: \"Modulus to take of the hash of the source label\n                        values. \\n Only applicable when the action is `HashMod`.\"\n                      format: int64\n                      type: integer\n                    regex:\n                      description: Regular expression against which the extracted\n                        value is matched.\n                      type: string\n                    replacement:\n                      description: \"Replacement value against which a Replace action\n                        is performed if the regular expression matches. \\n Regex capture\n                        groups are available.\"\n                      type: string\n                    separator:\n                      description: Separator is the string between concatenated SourceLabels.\n                      type: string\n                    sourceLabels:\n                      description: The source labels select values from existing labels.\n                        Their content is concatenated using the configured Separator\n                        and matched against the configured regular expression.\n                      items:\n                        description: LabelName is a valid Prometheus label name which\n                          may only contain ASCII letters, numbers, as well as underscores.\n                        pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                        type: string\n                      type: array\n                    targetLabel:\n                      description: \"Label to which the resulting string is written\n                        in a replacement. \\n It is mandatory for `Replace`, `HashMod`,\n                        `Lowercase`, `Uppercase`, `KeepEqual` and `DropEqual` actions.\n                        \\n Regex capture groups are available.\"\n                      type: string\n                  type: object\n                type: array\n              module:\n                description: 'The module to use for probing specifying how to probe\n                  the target. Example module configuring in the blackbox exporter:\n                  https://github.com/prometheus/blackbox_exporter/blob/master/example.yml'\n                type: string\n              oauth2:\n                description: OAuth2 for the URL. Only valid in Prometheus versions\n                  2.27.0 and newer.\n                properties:\n                  clientId:\n                    description: '`clientId` specifies a key of a Secret or ConfigMap\n                      containing the OAuth2 client''s ID.'\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  clientSecret:\n                    description: '`clientSecret` specifies a key of a Secret containing\n                      the OAuth2 client''s secret.'\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  endpointParams:\n                    additionalProperties:\n                      type: string\n                    description: '`endpointParams` configures the HTTP parameters\n                      to append to the token URL.'\n                    type: object\n                  scopes:\n                    description: '`scopes` defines the OAuth2 scopes used for the\n                      token request.'\n                    items:\n                      type: string\n                    type: array\n                  tokenUrl:\n                    description: '`tokenURL` configures the URL to fetch the token\n                      from.'\n                    minLength: 1\n                    type: string\n                required:\n                - clientId\n                - clientSecret\n                - tokenUrl\n                type: object\n              prober:\n                description: Specification for the prober to use for probing targets.\n                  The prober.URL parameter is required. Targets cannot be probed if\n                  left empty.\n                properties:\n                  path:\n                    default: /probe\n                    description: Path to collect metrics from. Defaults to `/probe`.\n                    type: string\n                  proxyUrl:\n                    description: Optional ProxyURL.\n                    type: string\n                  scheme:\n                    description: HTTP scheme to use for scraping. `http` and `https`\n                      are the expected values unless you rewrite the `__scheme__`\n                      label via relabeling. If empty, Prometheus uses the default\n                      value `http`.\n                    enum:\n                    - http\n                    - https\n                    type: string\n                  url:\n                    description: Mandatory URL of the prober.\n                    type: string\n                required:\n                - url\n                type: object\n              sampleLimit:\n                description: SampleLimit defines per-scrape limit on number of scraped\n                  samples that will be accepted.\n                format: int64\n                type: integer\n              scrapeTimeout:\n                description: Timeout for scraping metrics from the Prometheus exporter.\n                  If not specified, the Prometheus global scrape timeout is used.\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              targetLimit:\n                description: TargetLimit defines a limit on the number of scraped\n                  targets that will be accepted.\n                format: int64\n                type: integer\n              targets:\n                description: Targets defines a set of static or dynamically discovered\n                  targets to probe.\n                properties:\n                  ingress:\n                    description: ingress defines the Ingress objects to probe and\n                      the relabeling configuration. If `staticConfig` is also defined,\n                      `staticConfig` takes precedence.\n                    properties:\n                      namespaceSelector:\n                        description: From which namespaces to select Ingress objects.\n                        properties:\n                          any:\n                            description: Boolean describing whether all namespaces\n                              are selected in contrast to a list restricting them.\n                            type: boolean\n                          matchNames:\n                            description: List of namespace names to select from.\n                            items:\n                              type: string\n                            type: array\n                        type: object\n                      relabelingConfigs:\n                        description: 'RelabelConfigs to apply to the label set of\n                          the target before it gets scraped. The original ingress\n                          address is available via the `__tmp_prometheus_ingress_address`\n                          label. It can be used to customize the probed URL. The original\n                          scrape job''s name is available via the `__tmp_prometheus_job_name`\n                          label. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'\n                        items:\n                          description: \"RelabelConfig allows dynamic rewriting of\n                            the label set for targets, alerts, scraped samples and\n                            remote write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                          properties:\n                            action:\n                              default: replace\n                              description: \"Action to perform based on the regex matching.\n                                \\n `Uppercase` and `Lowercase` actions require Prometheus\n                                >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                                Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                              enum:\n                              - replace\n                              - Replace\n                              - keep\n                              - Keep\n                              - drop\n                              - Drop\n                              - hashmod\n                              - HashMod\n                              - labelmap\n                              - LabelMap\n                              - labeldrop\n                              - LabelDrop\n                              - labelkeep\n                              - LabelKeep\n                              - lowercase\n                              - Lowercase\n                              - uppercase\n                              - Uppercase\n                              - keepequal\n                              - KeepEqual\n                              - dropequal\n                              - DropEqual\n                              type: string\n                            modulus:\n                              description: \"Modulus to take of the hash of the source\n                                label values. \\n Only applicable when the action is\n                                `HashMod`.\"\n                              format: int64\n                              type: integer\n                            regex:\n                              description: Regular expression against which the extracted\n                                value is matched.\n                              type: string\n                            replacement:\n                              description: \"Replacement value against which a Replace\n                                action is performed if the regular expression matches.\n                                \\n Regex capture groups are available.\"\n                              type: string\n                            separator:\n                              description: Separator is the string between concatenated\n                                SourceLabels.\n                              type: string\n                            sourceLabels:\n                              description: The source labels select values from existing\n                                labels. Their content is concatenated using the configured\n                                Separator and matched against the configured regular\n                                expression.\n                              items:\n                                description: LabelName is a valid Prometheus label\n                                  name which may only contain ASCII letters, numbers,\n                                  as well as underscores.\n                                pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                                type: string\n                              type: array\n                            targetLabel:\n                              description: \"Label to which the resulting string is\n                                written in a replacement. \\n It is mandatory for `Replace`,\n                                `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                                `DropEqual` actions. \\n Regex capture groups are available.\"\n                              type: string\n                          type: object\n                        type: array\n                      selector:\n                        description: Selector to select the Ingress objects.\n                        properties:\n                          matchExpressions:\n                            description: matchExpressions is a list of label selector\n                              requirements. The requirements are ANDed.\n                            items:\n                              description: A label selector requirement is a selector\n                                that contains values, a key, and an operator that\n                                relates the key and values.\n                              properties:\n                                key:\n                                  description: key is the label key that the selector\n                                    applies to.\n                                  type: string\n                                operator:\n                                  description: operator represents a key's relationship\n                                    to a set of values. Valid operators are In, NotIn,\n                                    Exists and DoesNotExist.\n                                  type: string\n                                values:\n                                  description: values is an array of string values.\n                                    If the operator is In or NotIn, the values array\n                                    must be non-empty. If the operator is Exists or\n                                    DoesNotExist, the values array must be empty.\n                                    This array is replaced during a strategic merge\n                                    patch.\n                                  items:\n                                    type: string\n                                  type: array\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            description: matchLabels is a map of {key,value} pairs.\n                              A single {key,value} in the matchLabels map is equivalent\n                              to an element of matchExpressions, whose key field is\n                              \"key\", the operator is \"In\", and the values array contains\n                              only \"value\". The requirements are ANDed.\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  staticConfig:\n                    description: 'staticConfig defines the static list of targets\n                      to probe and the relabeling configuration. If `ingress` is also\n                      defined, `staticConfig` takes precedence. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config.'\n                    properties:\n                      labels:\n                        additionalProperties:\n                          type: string\n                        description: Labels assigned to all metrics scraped from the\n                          targets.\n                        type: object\n                      relabelingConfigs:\n                        description: 'RelabelConfigs to apply to the label set of\n                          the targets before it gets scraped. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'\n                        items:\n                          description: \"RelabelConfig allows dynamic rewriting of\n                            the label set for targets, alerts, scraped samples and\n                            remote write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                          properties:\n                            action:\n                              default: replace\n                              description: \"Action to perform based on the regex matching.\n                                \\n `Uppercase` and `Lowercase` actions require Prometheus\n                                >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                                Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                              enum:\n                              - replace\n                              - Replace\n                              - keep\n                              - Keep\n                              - drop\n                              - Drop\n                              - hashmod\n                              - HashMod\n                              - labelmap\n                              - LabelMap\n                              - labeldrop\n                              - LabelDrop\n                              - labelkeep\n                              - LabelKeep\n                              - lowercase\n                              - Lowercase\n                              - uppercase\n                              - Uppercase\n                              - keepequal\n                              - KeepEqual\n                              - dropequal\n                              - DropEqual\n                              type: string\n                            modulus:\n                              description: \"Modulus to take of the hash of the source\n                                label values. \\n Only applicable when the action is\n                                `HashMod`.\"\n                              format: int64\n                              type: integer\n                            regex:\n                              description: Regular expression against which the extracted\n                                value is matched.\n                              type: string\n                            replacement:\n                              description: \"Replacement value against which a Replace\n                                action is performed if the regular expression matches.\n                                \\n Regex capture groups are available.\"\n                              type: string\n                            separator:\n                              description: Separator is the string between concatenated\n                                SourceLabels.\n                              type: string\n                            sourceLabels:\n                              description: The source labels select values from existing\n                                labels. Their content is concatenated using the configured\n                                Separator and matched against the configured regular\n                                expression.\n                              items:\n                                description: LabelName is a valid Prometheus label\n                                  name which may only contain ASCII letters, numbers,\n                                  as well as underscores.\n                                pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                                type: string\n                              type: array\n                            targetLabel:\n                              description: \"Label to which the resulting string is\n                                written in a replacement. \\n It is mandatory for `Replace`,\n                                `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                                `DropEqual` actions. \\n Regex capture groups are available.\"\n                              type: string\n                          type: object\n                        type: array\n                      static:\n                        description: The list of hosts to probe.\n                        items:\n                          type: string\n                        type: array\n                    type: object\n                type: object\n              tlsConfig:\n                description: TLS configuration to use when scraping the endpoint.\n                properties:\n                  ca:\n                    description: Certificate authority used when verifying server\n                      certificates.\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  cert:\n                    description: Client certificate to present when doing client-authentication.\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  insecureSkipVerify:\n                    description: Disable target certificate validation.\n                    type: boolean\n                  keySecret:\n                    description: Secret containing the client key file for the targets.\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  serverName:\n                    description: Used to verify the hostname for the targets.\n                    type: string\n                type: object\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n"
  },
  {
    "path": "third_party/kube-prometheus-stack/01-crds.yaml",
    "content": "{{ if eq .Values.app_management \"true\" }}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: alertmanagerconfigs.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: AlertmanagerConfig\n    listKind: AlertmanagerConfigList\n    plural: alertmanagerconfigs\n    shortNames:\n    - amcfg\n    singular: alertmanagerconfig\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: AlertmanagerConfig configures the Prometheus Alertmanager, specifying\n          how alerts should be grouped, inhibited and notified to external systems.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: AlertmanagerConfigSpec is a specification of the desired\n              behavior of the Alertmanager configuration. By definition, the Alertmanager\n              configuration only applies to alerts for which the `namespace` label\n              is equal to the namespace of the AlertmanagerConfig resource.\n            properties:\n              inhibitRules:\n                description: List of inhibition rules. The rules will only apply to\n                  alerts matching the resource's namespace.\n                items:\n                  description: InhibitRule defines an inhibition rule that allows\n                    to mute alerts when other alerts are already firing. See https://prometheus.io/docs/alerting/latest/configuration/#inhibit_rule\n                  properties:\n                    equal:\n                      description: Labels that must have an equal value in the source\n                        and target alert for the inhibition to take effect.\n                      items:\n                        type: string\n                      type: array\n                    sourceMatch:\n                      description: Matchers for which one or more alerts have to exist\n                        for the inhibition to take effect. The operator enforces that\n                        the alert matches the resource's namespace.\n                      items:\n                        description: Matcher defines how to match on alert's labels.\n                        properties:\n                          matchType:\n                            description: Match operation available with AlertManager\n                              >= v0.22.0 and takes precedence over Regex (deprecated)\n                              if non-empty.\n                            enum:\n                            - '!='\n                            - =\n                            - =~\n                            - '!~'\n                            type: string\n                          name:\n                            description: Label to match.\n                            minLength: 1\n                            type: string\n                          regex:\n                            description: 'Whether to match on equality (false) or\n                              regular-expression (true). Deprecated: for AlertManager\n                              >= v0.22.0, `matchType` should be used instead.'\n                            type: boolean\n                          value:\n                            description: Label value to match.\n                            type: string\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    targetMatch:\n                      description: Matchers that have to be fulfilled in the alerts\n                        to be muted. The operator enforces that the alert matches\n                        the resource's namespace.\n                      items:\n                        description: Matcher defines how to match on alert's labels.\n                        properties:\n                          matchType:\n                            description: Match operation available with AlertManager\n                              >= v0.22.0 and takes precedence over Regex (deprecated)\n                              if non-empty.\n                            enum:\n                            - '!='\n                            - =\n                            - =~\n                            - '!~'\n                            type: string\n                          name:\n                            description: Label to match.\n                            minLength: 1\n                            type: string\n                          regex:\n                            description: 'Whether to match on equality (false) or\n                              regular-expression (true). Deprecated: for AlertManager\n                              >= v0.22.0, `matchType` should be used instead.'\n                            type: boolean\n                          value:\n                            description: Label value to match.\n                            type: string\n                        required:\n                        - name\n                        type: object\n                      type: array\n                  type: object\n                type: array\n              muteTimeIntervals:\n                description: List of MuteTimeInterval specifying when the routes should\n                  be muted.\n                items:\n                  description: MuteTimeInterval specifies the periods in time when\n                    notifications will be muted\n                  properties:\n                    name:\n                      description: Name of the time interval\n                      type: string\n                    timeIntervals:\n                      description: TimeIntervals is a list of TimeInterval\n                      items:\n                        description: TimeInterval describes intervals of time\n                        properties:\n                          daysOfMonth:\n                            description: DaysOfMonth is a list of DayOfMonthRange\n                            items:\n                              description: DayOfMonthRange is an inclusive range of\n                                days of the month beginning at 1\n                              properties:\n                                end:\n                                  description: End of the inclusive range\n                                  maximum: 31\n                                  minimum: -31\n                                  type: integer\n                                start:\n                                  description: Start of the inclusive range\n                                  maximum: 31\n                                  minimum: -31\n                                  type: integer\n                              type: object\n                            type: array\n                          months:\n                            description: Months is a list of MonthRange\n                            items:\n                              description: MonthRange is an inclusive range of months\n                                of the year beginning in January Months can be specified\n                                by name (e.g 'January') by numerical month (e.g '1')\n                                or as an inclusive range (e.g 'January:March', '1:3',\n                                '1:March')\n                              pattern: ^((?i)january|february|march|april|may|june|july|august|september|october|november|december|[1-12])(?:((:((?i)january|february|march|april|may|june|july|august|september|october|november|december|[1-12]))$)|$)\n                              type: string\n                            type: array\n                          times:\n                            description: Times is a list of TimeRange\n                            items:\n                              description: TimeRange defines a start and end time\n                                in 24hr format\n                              properties:\n                                endTime:\n                                  description: EndTime is the end time in 24hr format.\n                                  pattern: ^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)\n                                  type: string\n                                startTime:\n                                  description: StartTime is the start time in 24hr\n                                    format.\n                                  pattern: ^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)\n                                  type: string\n                              type: object\n                            type: array\n                          weekdays:\n                            description: Weekdays is a list of WeekdayRange\n                            items:\n                              description: WeekdayRange is an inclusive range of days\n                                of the week beginning on Sunday Days can be specified\n                                by name (e.g 'Sunday') or as an inclusive range (e.g\n                                'Monday:Friday')\n                              pattern: ^((?i)sun|mon|tues|wednes|thurs|fri|satur)day(?:((:(sun|mon|tues|wednes|thurs|fri|satur)day)$)|$)\n                              type: string\n                            type: array\n                          years:\n                            description: Years is a list of YearRange\n                            items:\n                              description: YearRange is an inclusive range of years\n                              pattern: ^2\\d{3}(?::2\\d{3}|$)\n                              type: string\n                            type: array\n                        type: object\n                      type: array\n                  type: object\n                type: array\n              receivers:\n                description: List of receivers.\n                items:\n                  description: Receiver defines one or more notification integrations.\n                  properties:\n                    discordConfigs:\n                      description: List of Discord configurations.\n                      items:\n                        description: DiscordConfig configures notifications via Discord.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#discord_config\n                        properties:\n                          apiURL:\n                            description: The secret's key that contains the Discord\n                              webhook URL. The secret needs to be in the same namespace\n                              as the AlertmanagerConfig object and accessible by the\n                              Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: The template of the message's body.\n                            type: string\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          title:\n                            description: The template of the message's title.\n                            type: string\n                        required:\n                        - apiURL\n                        type: object\n                      type: array\n                    emailConfigs:\n                      description: List of Email configurations.\n                      items:\n                        description: EmailConfig configures notifications via Email.\n                        properties:\n                          authIdentity:\n                            description: The identity to use for authentication.\n                            type: string\n                          authPassword:\n                            description: The secret's key that contains the password\n                              to use for authentication. The secret needs to be in\n                              the same namespace as the AlertmanagerConfig object\n                              and accessible by the Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          authSecret:\n                            description: The secret's key that contains the CRAM-MD5\n                              secret. The secret needs to be in the same namespace\n                              as the AlertmanagerConfig object and accessible by the\n                              Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          authUsername:\n                            description: The username to use for authentication.\n                            type: string\n                          from:\n                            description: The sender address.\n                            type: string\n                          headers:\n                            description: Further headers email header key/value pairs.\n                              Overrides any headers previously set by the notification\n                              implementation.\n                            items:\n                              description: KeyValue defines a (key, value) tuple.\n                              properties:\n                                key:\n                                  description: Key of the tuple.\n                                  minLength: 1\n                                  type: string\n                                value:\n                                  description: Value of the tuple.\n                                  type: string\n                              required:\n                              - key\n                              - value\n                              type: object\n                            type: array\n                          hello:\n                            description: The hostname to identify to the SMTP server.\n                            type: string\n                          html:\n                            description: The HTML body of the email notification.\n                            type: string\n                          requireTLS:\n                            description: The SMTP TLS requirement. Note that Go does\n                              not support unencrypted connections to remote SMTP endpoints.\n                            type: boolean\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          smarthost:\n                            description: The SMTP host and port through which emails\n                              are sent. E.g. example.com:25\n                            type: string\n                          text:\n                            description: The text body of the email notification.\n                            type: string\n                          tlsConfig:\n                            description: TLS configuration\n                            properties:\n                              ca:\n                                description: Certificate authority used when verifying\n                                  server certificates.\n                                properties:\n                                  configMap:\n                                    description: ConfigMap containing data to use\n                                      for the targets.\n                                    properties:\n                                      key:\n                                        description: The key to select.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the ConfigMap\n                                          or its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  secret:\n                                    description: Secret containing data to use for\n                                      the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              cert:\n                                description: Client certificate to present when doing\n                                  client-authentication.\n                                properties:\n                                  configMap:\n                                    description: ConfigMap containing data to use\n                                      for the targets.\n                                    properties:\n                                      key:\n                                        description: The key to select.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the ConfigMap\n                                          or its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  secret:\n                                    description: Secret containing data to use for\n                                      the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              insecureSkipVerify:\n                                description: Disable target certificate validation.\n                                type: boolean\n                              keySecret:\n                                description: Secret containing the client key file\n                                  for the targets.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              serverName:\n                                description: Used to verify the hostname for the targets.\n                                type: string\n                            type: object\n                          to:\n                            description: The email address to send notifications to.\n                            type: string\n                        type: object\n                      type: array\n                    msteamsConfigs:\n                      description: List of MSTeams configurations. It requires Alertmanager\n                        >= 0.26.0.\n                      items:\n                        description: MSTeamsConfig configures notifications via Microsoft\n                          Teams. It requires Alertmanager >= 0.26.0.\n                        properties:\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          sendResolved:\n                            description: Whether to notify about resolved alerts.\n                            type: boolean\n                          text:\n                            description: Message body template.\n                            type: string\n                          title:\n                            description: Message title template.\n                            type: string\n                          webhookUrl:\n                            description: MSTeams webhook URL.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        required:\n                        - webhookUrl\n                        type: object\n                      type: array\n                    name:\n                      description: Name of the receiver. Must be unique across all\n                        items from the list.\n                      minLength: 1\n                      type: string\n                    opsgenieConfigs:\n                      description: List of OpsGenie configurations.\n                      items:\n                        description: OpsGenieConfig configures notifications via OpsGenie.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#opsgenie_config\n                        properties:\n                          actions:\n                            description: Comma separated list of actions that will\n                              be available for the alert.\n                            type: string\n                          apiKey:\n                            description: The secret's key that contains the OpsGenie\n                              API key. The secret needs to be in the same namespace\n                              as the AlertmanagerConfig object and accessible by the\n                              Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          apiURL:\n                            description: The URL to send OpsGenie API requests to.\n                            type: string\n                          description:\n                            description: Description of the incident.\n                            type: string\n                          details:\n                            description: A set of arbitrary key/value pairs that provide\n                              further detail about the incident.\n                            items:\n                              description: KeyValue defines a (key, value) tuple.\n                              properties:\n                                key:\n                                  description: Key of the tuple.\n                                  minLength: 1\n                                  type: string\n                                value:\n                                  description: Value of the tuple.\n                                  type: string\n                              required:\n                              - key\n                              - value\n                              type: object\n                            type: array\n                          entity:\n                            description: Optional field that can be used to specify\n                              which domain alert is related to.\n                            type: string\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: Alert text limited to 130 characters.\n                            type: string\n                          note:\n                            description: Additional alert note.\n                            type: string\n                          priority:\n                            description: Priority level of alert. Possible values\n                              are P1, P2, P3, P4, and P5.\n                            type: string\n                          responders:\n                            description: List of responders responsible for notifications.\n                            items:\n                              description: OpsGenieConfigResponder defines a responder\n                                to an incident. One of `id`, `name` or `username`\n                                has to be defined.\n                              properties:\n                                id:\n                                  description: ID of the responder.\n                                  type: string\n                                name:\n                                  description: Name of the responder.\n                                  type: string\n                                type:\n                                  description: Type of responder.\n                                  enum:\n                                  - team\n                                  - teams\n                                  - user\n                                  - escalation\n                                  - schedule\n                                  minLength: 1\n                                  type: string\n                                username:\n                                  description: Username of the responder.\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            type: array\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          source:\n                            description: Backlink to the sender of the notification.\n                            type: string\n                          tags:\n                            description: Comma separated list of tags attached to\n                              the notifications.\n                            type: string\n                          updateAlerts:\n                            description: Whether to update message and description\n                              of the alert in OpsGenie if it already exists By default,\n                              the alert is never updated in OpsGenie, the new message\n                              only appears in activity log.\n                            type: boolean\n                        type: object\n                      type: array\n                    pagerdutyConfigs:\n                      description: List of PagerDuty configurations.\n                      items:\n                        description: PagerDutyConfig configures notifications via\n                          PagerDuty. See https://prometheus.io/docs/alerting/latest/configuration/#pagerduty_config\n                        properties:\n                          class:\n                            description: The class/type of the event.\n                            type: string\n                          client:\n                            description: Client identification.\n                            type: string\n                          clientURL:\n                            description: Backlink to the sender of notification.\n                            type: string\n                          component:\n                            description: The part or component of the affected system\n                              that is broken.\n                            type: string\n                          description:\n                            description: Description of the incident.\n                            type: string\n                          details:\n                            description: Arbitrary key/value pairs that provide further\n                              detail about the incident.\n                            items:\n                              description: KeyValue defines a (key, value) tuple.\n                              properties:\n                                key:\n                                  description: Key of the tuple.\n                                  minLength: 1\n                                  type: string\n                                value:\n                                  description: Value of the tuple.\n                                  type: string\n                              required:\n                              - key\n                              - value\n                              type: object\n                            type: array\n                          group:\n                            description: A cluster or grouping of sources.\n                            type: string\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          pagerDutyImageConfigs:\n                            description: A list of image details to attach that provide\n                              further detail about an incident.\n                            items:\n                              description: PagerDutyImageConfig attaches images to\n                                an incident\n                              properties:\n                                alt:\n                                  description: Alt is the optional alternative text\n                                    for the image.\n                                  type: string\n                                href:\n                                  description: Optional URL; makes the image a clickable\n                                    link.\n                                  type: string\n                                src:\n                                  description: Src of the image being attached to\n                                    the incident\n                                  type: string\n                              type: object\n                            type: array\n                          pagerDutyLinkConfigs:\n                            description: A list of link details to attach that provide\n                              further detail about an incident.\n                            items:\n                              description: PagerDutyLinkConfig attaches text links\n                                to an incident\n                              properties:\n                                alt:\n                                  description: Text that describes the purpose of\n                                    the link, and can be used as the link's text.\n                                  type: string\n                                href:\n                                  description: Href is the URL of the link to be attached\n                                  type: string\n                              type: object\n                            type: array\n                          routingKey:\n                            description: The secret's key that contains the PagerDuty\n                              integration key (when using Events API v2). Either this\n                              field or `serviceKey` needs to be defined. The secret\n                              needs to be in the same namespace as the AlertmanagerConfig\n                              object and accessible by the Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          serviceKey:\n                            description: The secret's key that contains the PagerDuty\n                              service key (when using integration type \"Prometheus\").\n                              Either this field or `routingKey` needs to be defined.\n                              The secret needs to be in the same namespace as the\n                              AlertmanagerConfig object and accessible by the Prometheus\n                              Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          severity:\n                            description: Severity of the incident.\n                            type: string\n                          url:\n                            description: The URL to send requests to.\n                            type: string\n                        type: object\n                      type: array\n                    pushoverConfigs:\n                      description: List of Pushover configurations.\n                      items:\n                        description: PushoverConfig configures notifications via Pushover.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#pushover_config\n                        properties:\n                          device:\n                            description: The name of a device to send the notification\n                              to\n                            type: string\n                          expire:\n                            description: How long your notification will continue\n                              to be retried for, unless the user acknowledges the\n                              notification.\n                            pattern: ^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$\n                            type: string\n                          html:\n                            description: Whether notification message is HTML or plain\n                              text.\n                            type: boolean\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: Notification message.\n                            type: string\n                          priority:\n                            description: Priority, see https://pushover.net/api#priority\n                            type: string\n                          retry:\n                            description: How often the Pushover servers will send\n                              the same notification to the user. Must be at least\n                              30 seconds.\n                            pattern: ^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$\n                            type: string\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          sound:\n                            description: The name of one of the sounds supported by\n                              device clients to override the user's default sound\n                              choice\n                            type: string\n                          title:\n                            description: Notification title.\n                            type: string\n                          token:\n                            description: The secret's key that contains the registered\n                              application's API token, see https://pushover.net/apps.\n                              The secret needs to be in the same namespace as the\n                              AlertmanagerConfig object and accessible by the Prometheus\n                              Operator. Either `token` or `tokenFile` is required.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          tokenFile:\n                            description: The token file that contains the registered\n                              application's API token, see https://pushover.net/apps.\n                              Either `token` or `tokenFile` is required. It requires\n                              Alertmanager >= v0.26.0.\n                            type: string\n                          url:\n                            description: A supplementary URL shown alongside the message.\n                            type: string\n                          urlTitle:\n                            description: A title for supplementary URL, otherwise\n                              just the URL is shown\n                            type: string\n                          userKey:\n                            description: The secret's key that contains the recipient\n                              user's user key. The secret needs to be in the same\n                              namespace as the AlertmanagerConfig object and accessible\n                              by the Prometheus Operator. Either `userKey` or `userKeyFile`\n                              is required.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          userKeyFile:\n                            description: The user key file that contains the recipient\n                              user's user key. Either `userKey` or `userKeyFile` is\n                              required. It requires Alertmanager >= v0.26.0.\n                            type: string\n                        type: object\n                      type: array\n                    slackConfigs:\n                      description: List of Slack configurations.\n                      items:\n                        description: SlackConfig configures notifications via Slack.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#slack_config\n                        properties:\n                          actions:\n                            description: A list of Slack actions that are sent with\n                              each notification.\n                            items:\n                              description: SlackAction configures a single Slack action\n                                that is sent with each notification. See https://api.slack.com/docs/message-attachments#action_fields\n                                and https://api.slack.com/docs/message-buttons for\n                                more information.\n                              properties:\n                                confirm:\n                                  description: SlackConfirmationField protect users\n                                    from destructive actions or particularly distinguished\n                                    decisions by asking them to confirm their button\n                                    click one more time. See https://api.slack.com/docs/interactive-message-field-guide#confirmation_fields\n                                    for more information.\n                                  properties:\n                                    dismissText:\n                                      type: string\n                                    okText:\n                                      type: string\n                                    text:\n                                      minLength: 1\n                                      type: string\n                                    title:\n                                      type: string\n                                  required:\n                                  - text\n                                  type: object\n                                name:\n                                  type: string\n                                style:\n                                  type: string\n                                text:\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  minLength: 1\n                                  type: string\n                                url:\n                                  type: string\n                                value:\n                                  type: string\n                              required:\n                              - text\n                              - type\n                              type: object\n                            type: array\n                          apiURL:\n                            description: The secret's key that contains the Slack\n                              webhook URL. The secret needs to be in the same namespace\n                              as the AlertmanagerConfig object and accessible by the\n                              Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          callbackId:\n                            type: string\n                          channel:\n                            description: The channel or user to send notifications\n                              to.\n                            type: string\n                          color:\n                            type: string\n                          fallback:\n                            type: string\n                          fields:\n                            description: A list of Slack fields that are sent with\n                              each notification.\n                            items:\n                              description: SlackField configures a single Slack field\n                                that is sent with each notification. Each field must\n                                contain a title, value, and optionally, a boolean\n                                value to indicate if the field is short enough to\n                                be displayed next to other fields designated as short.\n                                See https://api.slack.com/docs/message-attachments#fields\n                                for more information.\n                              properties:\n                                short:\n                                  type: boolean\n                                title:\n                                  minLength: 1\n                                  type: string\n                                value:\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - title\n                              - value\n                              type: object\n                            type: array\n                          footer:\n                            type: string\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          iconEmoji:\n                            type: string\n                          iconURL:\n                            type: string\n                          imageURL:\n                            type: string\n                          linkNames:\n                            type: boolean\n                          mrkdwnIn:\n                            items:\n                              type: string\n                            type: array\n                          pretext:\n                            type: string\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          shortFields:\n                            type: boolean\n                          text:\n                            type: string\n                          thumbURL:\n                            type: string\n                          title:\n                            type: string\n                          titleLink:\n                            type: string\n                          username:\n                            type: string\n                        type: object\n                      type: array\n                    snsConfigs:\n                      description: List of SNS configurations\n                      items:\n                        description: SNSConfig configures notifications via AWS SNS.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#sns_configs\n                        properties:\n                          apiURL:\n                            description: The SNS API URL i.e. https://sns.us-east-2.amazonaws.com.\n                              If not specified, the SNS API URL from the SNS SDK will\n                              be used.\n                            type: string\n                          attributes:\n                            additionalProperties:\n                              type: string\n                            description: SNS message attributes.\n                            type: object\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: The message content of the SNS notification.\n                            type: string\n                          phoneNumber:\n                            description: Phone number if message is delivered via\n                              SMS in E.164 format. If you don't specify this value,\n                              you must specify a value for the TopicARN or TargetARN.\n                            type: string\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          sigv4:\n                            description: Configures AWS's Signature Verification 4\n                              signing process to sign requests.\n                            properties:\n                              accessKey:\n                                description: AccessKey is the AWS API key. If not\n                                  specified, the environment variable `AWS_ACCESS_KEY_ID`\n                                  is used.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              profile:\n                                description: Profile is the named AWS profile used\n                                  to authenticate.\n                                type: string\n                              region:\n                                description: Region is the AWS region. If blank, the\n                                  region from the default credentials chain used.\n                                type: string\n                              roleArn:\n                                description: RoleArn is the named AWS profile used\n                                  to authenticate.\n                                type: string\n                              secretKey:\n                                description: SecretKey is the AWS API secret. If not\n                                  specified, the environment variable `AWS_SECRET_ACCESS_KEY`\n                                  is used.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                          subject:\n                            description: Subject line when the message is delivered\n                              to email endpoints.\n                            type: string\n                          targetARN:\n                            description: The  mobile platform endpoint ARN if message\n                              is delivered via mobile notifications. If you don't\n                              specify this value, you must specify a value for the\n                              topic_arn or PhoneNumber.\n                            type: string\n                          topicARN:\n                            description: SNS topic ARN, i.e. arn:aws:sns:us-east-2:698519295917:My-Topic\n                              If you don't specify this value, you must specify a\n                              value for the PhoneNumber or TargetARN.\n                            type: string\n                        type: object\n                      type: array\n                    telegramConfigs:\n                      description: List of Telegram configurations.\n                      items:\n                        description: TelegramConfig configures notifications via Telegram.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#telegram_config\n                        properties:\n                          apiURL:\n                            description: The Telegram API URL i.e. https://api.telegram.org.\n                              If not specified, default API URL will be used.\n                            type: string\n                          botToken:\n                            description: \"Telegram bot token. It is mutually exclusive\n                              with `botTokenFile`. The secret needs to be in the same\n                              namespace as the AlertmanagerConfig object and accessible\n                              by the Prometheus Operator. \\n Either `botToken` or\n                              `botTokenFile` is required.\"\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          botTokenFile:\n                            description: \"File to read the Telegram bot token from.\n                              It is mutually exclusive with `botToken`. Either `botToken`\n                              or `botTokenFile` is required. \\n It requires Alertmanager\n                              >= v0.26.0.\"\n                            type: string\n                          chatID:\n                            description: The Telegram chat ID.\n                            format: int64\n                            type: integer\n                          disableNotifications:\n                            description: Disable telegram notifications\n                            type: boolean\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: Message template\n                            type: string\n                          parseMode:\n                            description: Parse mode for telegram message\n                            enum:\n                            - MarkdownV2\n                            - Markdown\n                            - HTML\n                            type: string\n                          sendResolved:\n                            description: Whether to notify about resolved alerts.\n                            type: boolean\n                        type: object\n                      type: array\n                    victoropsConfigs:\n                      description: List of VictorOps configurations.\n                      items:\n                        description: VictorOpsConfig configures notifications via\n                          VictorOps. See https://prometheus.io/docs/alerting/latest/configuration/#victorops_config\n                        properties:\n                          apiKey:\n                            description: The secret's key that contains the API key\n                              to use when talking to the VictorOps API. The secret\n                              needs to be in the same namespace as the AlertmanagerConfig\n                              object and accessible by the Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          apiUrl:\n                            description: The VictorOps API URL.\n                            type: string\n                          customFields:\n                            description: Additional custom fields for notification.\n                            items:\n                              description: KeyValue defines a (key, value) tuple.\n                              properties:\n                                key:\n                                  description: Key of the tuple.\n                                  minLength: 1\n                                  type: string\n                                value:\n                                  description: Value of the tuple.\n                                  type: string\n                              required:\n                              - key\n                              - value\n                              type: object\n                            type: array\n                          entityDisplayName:\n                            description: Contains summary of the alerted problem.\n                            type: string\n                          httpConfig:\n                            description: The HTTP client's configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          messageType:\n                            description: Describes the behavior of the alert (CRITICAL,\n                              WARNING, INFO).\n                            type: string\n                          monitoringTool:\n                            description: The monitoring tool the state message is\n                              from.\n                            type: string\n                          routingKey:\n                            description: A key used to map the alert to a team.\n                            type: string\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          stateMessage:\n                            description: Contains long explanation of the alerted\n                              problem.\n                            type: string\n                        type: object\n                      type: array\n                    webexConfigs:\n                      description: List of Webex configurations.\n                      items:\n                        description: WebexConfig configures notification via Cisco\n                          Webex See https://prometheus.io/docs/alerting/latest/configuration/#webex_config\n                        properties:\n                          apiURL:\n                            description: The Webex Teams API URL i.e. https://webexapis.com/v1/messages\n                              Provide if different from the default API URL.\n                            pattern: ^https?://.+$\n                            type: string\n                          httpConfig:\n                            description: The HTTP client's configuration. You must\n                              supply the bot token via the `httpConfig.authorization`\n                              field.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: Message template\n                            type: string\n                          roomID:\n                            description: ID of the Webex Teams room where to send\n                              the messages.\n                            minLength: 1\n                            type: string\n                          sendResolved:\n                            description: Whether to notify about resolved alerts.\n                            type: boolean\n                        required:\n                        - roomID\n                        type: object\n                      type: array\n                    webhookConfigs:\n                      description: List of webhook configurations.\n                      items:\n                        description: WebhookConfig configures notifications via a\n                          generic receiver supporting the webhook payload. See https://prometheus.io/docs/alerting/latest/configuration/#webhook_config\n                        properties:\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          maxAlerts:\n                            description: Maximum number of alerts to be sent per webhook\n                              message. When 0, all alerts are included.\n                            format: int32\n                            minimum: 0\n                            type: integer\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          url:\n                            description: The URL to send HTTP POST requests to. `urlSecret`\n                              takes precedence over `url`. One of `urlSecret` and\n                              `url` should be defined.\n                            type: string\n                          urlSecret:\n                            description: The secret's key that contains the webhook\n                              URL to send HTTP requests to. `urlSecret` takes precedence\n                              over `url`. One of `urlSecret` and `url` should be defined.\n                              The secret needs to be in the same namespace as the\n                              AlertmanagerConfig object and accessible by the Prometheus\n                              Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    wechatConfigs:\n                      description: List of WeChat configurations.\n                      items:\n                        description: WeChatConfig configures notifications via WeChat.\n                          See https://prometheus.io/docs/alerting/latest/configuration/#wechat_config\n                        properties:\n                          agentID:\n                            type: string\n                          apiSecret:\n                            description: The secret's key that contains the WeChat\n                              API key. The secret needs to be in the same namespace\n                              as the AlertmanagerConfig object and accessible by the\n                              Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          apiURL:\n                            description: The WeChat API URL.\n                            type: string\n                          corpID:\n                            description: The corp id for authentication.\n                            type: string\n                          httpConfig:\n                            description: HTTP client configuration.\n                            properties:\n                              authorization:\n                                description: Authorization header configuration for\n                                  the client. This is mutually exclusive with BasicAuth\n                                  and is only available starting from Alertmanager\n                                  v0.22+.\n                                properties:\n                                  credentials:\n                                    description: Selects a key of a Secret in the\n                                      namespace that contains the credentials for\n                                      authentication.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  type:\n                                    description: \"Defines the authentication type.\n                                      The value is case-insensitive. \\n \\\"Basic\\\"\n                                      is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                                    type: string\n                                type: object\n                              basicAuth:\n                                description: BasicAuth for the client. This is mutually\n                                  exclusive with Authorization. If both are defined,\n                                  BasicAuth takes precedence.\n                                properties:\n                                  password:\n                                    description: '`password` specifies a key of a\n                                      Secret containing the password for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  username:\n                                    description: '`username` specifies a key of a\n                                      Secret containing the username for authentication.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              bearerTokenSecret:\n                                description: The secret's key that contains the bearer\n                                  token to be used by the client for authentication.\n                                  The secret needs to be in the same namespace as\n                                  the AlertmanagerConfig object and accessible by\n                                  the Prometheus Operator.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              followRedirects:\n                                description: FollowRedirects specifies whether the\n                                  client should follow HTTP 3xx redirects.\n                                type: boolean\n                              oauth2:\n                                description: OAuth2 client credentials used to fetch\n                                  a token for the targets.\n                                properties:\n                                  clientId:\n                                    description: '`clientId` specifies a key of a\n                                      Secret or ConfigMap containing the OAuth2 client''s\n                                      ID.'\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  clientSecret:\n                                    description: '`clientSecret` specifies a key of\n                                      a Secret containing the OAuth2 client''s secret.'\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  endpointParams:\n                                    additionalProperties:\n                                      type: string\n                                    description: '`endpointParams` configures the\n                                      HTTP parameters to append to the token URL.'\n                                    type: object\n                                  scopes:\n                                    description: '`scopes` defines the OAuth2 scopes\n                                      used for the token request.'\n                                    items:\n                                      type: string\n                                    type: array\n                                  tokenUrl:\n                                    description: '`tokenURL` configures the URL to\n                                      fetch the token from.'\n                                    minLength: 1\n                                    type: string\n                                required:\n                                - clientId\n                                - clientSecret\n                                - tokenUrl\n                                type: object\n                              proxyURL:\n                                description: Optional proxy URL.\n                                type: string\n                              tlsConfig:\n                                description: TLS configuration for the client.\n                                properties:\n                                  ca:\n                                    description: Certificate authority used when verifying\n                                      server certificates.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  cert:\n                                    description: Client certificate to present when\n                                      doing client-authentication.\n                                    properties:\n                                      configMap:\n                                        description: ConfigMap containing data to\n                                          use for the targets.\n                                        properties:\n                                          key:\n                                            description: The key to select.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the ConfigMap\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                      secret:\n                                        description: Secret containing data to use\n                                          for the targets.\n                                        properties:\n                                          key:\n                                            description: The key of the secret to\n                                              select from.  Must be a valid secret\n                                              key.\n                                            type: string\n                                          name:\n                                            description: 'Name of the referent. More\n                                              info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                              TODO: Add other useful fields. apiVersion,\n                                              kind, uid?'\n                                            type: string\n                                          optional:\n                                            description: Specify whether the Secret\n                                              or its key must be defined\n                                            type: boolean\n                                        required:\n                                        - key\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  insecureSkipVerify:\n                                    description: Disable target certificate validation.\n                                    type: boolean\n                                  keySecret:\n                                    description: Secret containing the client key\n                                      file for the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  serverName:\n                                    description: Used to verify the hostname for the\n                                      targets.\n                                    type: string\n                                type: object\n                            type: object\n                          message:\n                            description: API request data as defined by the WeChat\n                              API.\n                            type: string\n                          messageType:\n                            type: string\n                          sendResolved:\n                            description: Whether or not to notify about resolved alerts.\n                            type: boolean\n                          toParty:\n                            type: string\n                          toTag:\n                            type: string\n                          toUser:\n                            type: string\n                        type: object\n                      type: array\n                  required:\n                  - name\n                  type: object\n                type: array\n              route:\n                description: The Alertmanager route definition for alerts matching\n                  the resource's namespace. If present, it will be added to the generated\n                  Alertmanager configuration as a first-level route.\n                properties:\n                  activeTimeIntervals:\n                    description: ActiveTimeIntervals is a list of MuteTimeInterval\n                      names when this route should be active.\n                    items:\n                      type: string\n                    type: array\n                  continue:\n                    description: Boolean indicating whether an alert should continue\n                      matching subsequent sibling nodes. It will always be overridden\n                      to true for the first-level route by the Prometheus operator.\n                    type: boolean\n                  groupBy:\n                    description: List of labels to group by. Labels must not be repeated\n                      (unique list). Special label \"...\" (aggregate by all possible\n                      labels), if provided, must be the only element in the list.\n                    items:\n                      type: string\n                    type: array\n                  groupInterval:\n                    description: 'How long to wait before sending an updated notification.\n                      Must match the regular expression`^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$`\n                      Example: \"5m\"'\n                    type: string\n                  groupWait:\n                    description: 'How long to wait before sending the initial notification.\n                      Must match the regular expression`^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$`\n                      Example: \"30s\"'\n                    type: string\n                  matchers:\n                    description: 'List of matchers that the alert''s labels should\n                      match. For the first level route, the operator removes any existing\n                      equality and regexp matcher on the `namespace` label and adds\n                      a `namespace: <object namespace>` matcher.'\n                    items:\n                      description: Matcher defines how to match on alert's labels.\n                      properties:\n                        matchType:\n                          description: Match operation available with AlertManager\n                            >= v0.22.0 and takes precedence over Regex (deprecated)\n                            if non-empty.\n                          enum:\n                          - '!='\n                          - =\n                          - =~\n                          - '!~'\n                          type: string\n                        name:\n                          description: Label to match.\n                          minLength: 1\n                          type: string\n                        regex:\n                          description: 'Whether to match on equality (false) or regular-expression\n                            (true). Deprecated: for AlertManager >= v0.22.0, `matchType`\n                            should be used instead.'\n                          type: boolean\n                        value:\n                          description: Label value to match.\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    type: array\n                  muteTimeIntervals:\n                    description: 'Note: this comment applies to the field definition\n                      above but appears below otherwise it gets included in the generated\n                      manifest. CRD schema doesn''t support self-referential types\n                      for now (see https://github.com/kubernetes/kubernetes/issues/62872).\n                      We have to use an alternative type to circumvent the limitation.\n                      The downside is that the Kube API can''t validate the data beyond\n                      the fact that it is a valid JSON representation. MuteTimeIntervals\n                      is a list of MuteTimeInterval names that will mute this route\n                      when matched,'\n                    items:\n                      type: string\n                    type: array\n                  receiver:\n                    description: Name of the receiver for this route. If not empty,\n                      it should be listed in the `receivers` field.\n                    type: string\n                  repeatInterval:\n                    description: 'How long to wait before repeating the last notification.\n                      Must match the regular expression`^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$`\n                      Example: \"4h\"'\n                    type: string\n                  routes:\n                    description: Child routes.\n                    items:\n                      x-kubernetes-preserve-unknown-fields: true\n                    type: array\n                type: object\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: alertmanagers.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: Alertmanager\n    listKind: AlertmanagerList\n    plural: alertmanagers\n    shortNames:\n    - am\n    singular: alertmanager\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - description: The version of Alertmanager\n      jsonPath: .spec.version\n      name: Version\n      type: string\n    - description: The number of desired replicas\n      jsonPath: .spec.replicas\n      name: Replicas\n      type: integer\n    - description: The number of ready replicas\n      jsonPath: .status.availableReplicas\n      name: Ready\n      type: integer\n    - jsonPath: .status.conditions[?(@.type == 'Reconciled')].status\n      name: Reconciled\n      type: string\n    - jsonPath: .status.conditions[?(@.type == 'Available')].status\n      name: Available\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - description: Whether the resource reconciliation is paused or not\n      jsonPath: .status.paused\n      name: Paused\n      priority: 1\n      type: boolean\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: Alertmanager describes an Alertmanager cluster.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: 'Specification of the desired behavior of the Alertmanager\n              cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n            properties:\n              additionalPeers:\n                description: AdditionalPeers allows injecting a set of additional\n                  Alertmanagers to peer with to form a highly available cluster.\n                items:\n                  type: string\n                type: array\n              affinity:\n                description: If specified, the pod's scheduling constraints.\n                properties:\n                  nodeAffinity:\n                    description: Describes node affinity scheduling rules for the\n                      pod.\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the affinity expressions specified by\n                          this field, but it may choose a node that violates one or\n                          more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node matches\n                          the corresponding matchExpressions; the node(s) with the\n                          highest sum are the most preferred.\n                        items:\n                          description: An empty preferred scheduling term matches\n                            all objects with implicit weight 0 (i.e. it's a no-op).\n                            A null preferred scheduling term matches no objects (i.e.\n                            is also a no-op).\n                          properties:\n                            preference:\n                              description: A node selector term, associated with the\n                                corresponding weight.\n                              properties:\n                                matchExpressions:\n                                  description: A list of node selector requirements\n                                    by node's labels.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchFields:\n                                  description: A list of node selector requirements\n                                    by node's fields.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            weight:\n                              description: Weight associated with matching the corresponding\n                                nodeSelectorTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - preference\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the affinity requirements specified by this\n                          field are not met at scheduling time, the pod will not be\n                          scheduled onto the node. If the affinity requirements specified\n                          by this field cease to be met at some point during pod execution\n                          (e.g. due to an update), the system may or may not try to\n                          eventually evict the pod from its node.\n                        properties:\n                          nodeSelectorTerms:\n                            description: Required. A list of node selector terms.\n                              The terms are ORed.\n                            items:\n                              description: A null or empty node selector term matches\n                                no objects. The requirements of them are ANDed. The\n                                TopologySelectorTerm type implements a subset of the\n                                NodeSelectorTerm.\n                              properties:\n                                matchExpressions:\n                                  description: A list of node selector requirements\n                                    by node's labels.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchFields:\n                                  description: A list of node selector requirements\n                                    by node's fields.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            type: array\n                        required:\n                        - nodeSelectorTerms\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  podAffinity:\n                    description: Describes pod affinity scheduling rules (e.g. co-locate\n                      this pod in the same node, zone, etc. as some other pod(s)).\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the affinity expressions specified by\n                          this field, but it may choose a node that violates one or\n                          more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node has\n                          pods which matches the corresponding podAffinityTerm; the\n                          node(s) with the highest sum are the most preferred.\n                        items:\n                          description: The weights of all of the matched WeightedPodAffinityTerm\n                            fields are added per-node to find the most preferred node(s)\n                          properties:\n                            podAffinityTerm:\n                              description: Required. A pod affinity term, associated\n                                with the corresponding weight.\n                              properties:\n                                labelSelector:\n                                  description: A label query over a set of resources,\n                                    in this case pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaceSelector:\n                                  description: A label query over the set of namespaces\n                                    that the term applies to. The term is applied\n                                    to the union of the namespaces selected by this\n                                    field and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list\n                                    means \"this pod's namespace\". An empty selector\n                                    ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: namespaces specifies a static list\n                                    of namespace names that the term applies to. The\n                                    term is applied to the union of the namespaces\n                                    listed in this field and the ones selected by\n                                    namespaceSelector. null or empty namespaces list\n                                    and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                topologyKey:\n                                  description: This pod should be co-located (affinity)\n                                    or not co-located (anti-affinity) with the pods\n                                    matching the labelSelector in the specified namespaces,\n                                    where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey\n                                    matches that of any node on which any of the selected\n                                    pods is running. Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            weight:\n                              description: weight associated with matching the corresponding\n                                podAffinityTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - podAffinityTerm\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the affinity requirements specified by this\n                          field are not met at scheduling time, the pod will not be\n                          scheduled onto the node. If the affinity requirements specified\n                          by this field cease to be met at some point during pod execution\n                          (e.g. due to a pod label update), the system may or may\n                          not try to eventually evict the pod from its node. When\n                          there are multiple elements, the lists of nodes corresponding\n                          to each podAffinityTerm are intersected, i.e. all terms\n                          must be satisfied.\n                        items:\n                          description: Defines a set of pods (namely those matching\n                            the labelSelector relative to the given namespace(s))\n                            that this pod should be co-located (affinity) or not co-located\n                            (anti-affinity) with, where co-located is defined as running\n                            on a node whose value of the label with key <topologyKey>\n                            matches that of any node on which a pod of the set of\n                            pods is running\n                          properties:\n                            labelSelector:\n                              description: A label query over a set of resources,\n                                in this case pods.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaceSelector:\n                              description: A label query over the set of namespaces\n                                that the term applies to. The term is applied to the\n                                union of the namespaces selected by this field and\n                                the ones listed in the namespaces field. null selector\n                                and null or empty namespaces list means \"this pod's\n                                namespace\". An empty selector ({}) matches all namespaces.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaces:\n                              description: namespaces specifies a static list of namespace\n                                names that the term applies to. The term is applied\n                                to the union of the namespaces listed in this field\n                                and the ones selected by namespaceSelector. null or\n                                empty namespaces list and null namespaceSelector means\n                                \"this pod's namespace\".\n                              items:\n                                type: string\n                              type: array\n                            topologyKey:\n                              description: This pod should be co-located (affinity)\n                                or not co-located (anti-affinity) with the pods matching\n                                the labelSelector in the specified namespaces, where\n                                co-located is defined as running on a node whose value\n                                of the label with key topologyKey matches that of\n                                any node on which any of the selected pods is running.\n                                Empty topologyKey is not allowed.\n                              type: string\n                          required:\n                          - topologyKey\n                          type: object\n                        type: array\n                    type: object\n                  podAntiAffinity:\n                    description: Describes pod anti-affinity scheduling rules (e.g.\n                      avoid putting this pod in the same node, zone, etc. as some\n                      other pod(s)).\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the anti-affinity expressions specified\n                          by this field, but it may choose a node that violates one\n                          or more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling anti-affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node has\n                          pods which matches the corresponding podAffinityTerm; the\n                          node(s) with the highest sum are the most preferred.\n                        items:\n                          description: The weights of all of the matched WeightedPodAffinityTerm\n                            fields are added per-node to find the most preferred node(s)\n                          properties:\n                            podAffinityTerm:\n                              description: Required. A pod affinity term, associated\n                                with the corresponding weight.\n                              properties:\n                                labelSelector:\n                                  description: A label query over a set of resources,\n                                    in this case pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaceSelector:\n                                  description: A label query over the set of namespaces\n                                    that the term applies to. The term is applied\n                                    to the union of the namespaces selected by this\n                                    field and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list\n                                    means \"this pod's namespace\". An empty selector\n                                    ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: namespaces specifies a static list\n                                    of namespace names that the term applies to. The\n                                    term is applied to the union of the namespaces\n                                    listed in this field and the ones selected by\n                                    namespaceSelector. null or empty namespaces list\n                                    and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                topologyKey:\n                                  description: This pod should be co-located (affinity)\n                                    or not co-located (anti-affinity) with the pods\n                                    matching the labelSelector in the specified namespaces,\n                                    where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey\n                                    matches that of any node on which any of the selected\n                                    pods is running. Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            weight:\n                              description: weight associated with matching the corresponding\n                                podAffinityTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - podAffinityTerm\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the anti-affinity requirements specified by\n                          this field are not met at scheduling time, the pod will\n                          not be scheduled onto the node. If the anti-affinity requirements\n                          specified by this field cease to be met at some point during\n                          pod execution (e.g. due to a pod label update), the system\n                          may or may not try to eventually evict the pod from its\n                          node. When there are multiple elements, the lists of nodes\n                          corresponding to each podAffinityTerm are intersected, i.e.\n                          all terms must be satisfied.\n                        items:\n                          description: Defines a set of pods (namely those matching\n                            the labelSelector relative to the given namespace(s))\n                            that this pod should be co-located (affinity) or not co-located\n                            (anti-affinity) with, where co-located is defined as running\n                            on a node whose value of the label with key <topologyKey>\n                            matches that of any node on which a pod of the set of\n                            pods is running\n                          properties:\n                            labelSelector:\n                              description: A label query over a set of resources,\n                                in this case pods.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaceSelector:\n                              description: A label query over the set of namespaces\n                                that the term applies to. The term is applied to the\n                                union of the namespaces selected by this field and\n                                the ones listed in the namespaces field. null selector\n                                and null or empty namespaces list means \"this pod's\n                                namespace\". An empty selector ({}) matches all namespaces.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaces:\n                              description: namespaces specifies a static list of namespace\n                                names that the term applies to. The term is applied\n                                to the union of the namespaces listed in this field\n                                and the ones selected by namespaceSelector. null or\n                                empty namespaces list and null namespaceSelector means\n                                \"this pod's namespace\".\n                              items:\n                                type: string\n                              type: array\n                            topologyKey:\n                              description: This pod should be co-located (affinity)\n                                or not co-located (anti-affinity) with the pods matching\n                                the labelSelector in the specified namespaces, where\n                                co-located is defined as running on a node whose value\n                                of the label with key topologyKey matches that of\n                                any node on which any of the selected pods is running.\n                                Empty topologyKey is not allowed.\n                              type: string\n                          required:\n                          - topologyKey\n                          type: object\n                        type: array\n                    type: object\n                type: object\n              alertmanagerConfigMatcherStrategy:\n                description: The AlertmanagerConfigMatcherStrategy defines how AlertmanagerConfig\n                  objects match the alerts. In the future more options may be added.\n                properties:\n                  type:\n                    default: OnNamespace\n                    description: If set to `OnNamespace`, the operator injects a label\n                      matcher matching the namespace of the AlertmanagerConfig object\n                      for all its routes and inhibition rules. `None` will not add\n                      any additional matchers other than the ones specified in the\n                      AlertmanagerConfig. Default is `OnNamespace`.\n                    enum:\n                    - OnNamespace\n                    - None\n                    type: string\n                type: object\n              alertmanagerConfigNamespaceSelector:\n                description: Namespaces to be selected for AlertmanagerConfig discovery.\n                  If nil, only check own namespace.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              alertmanagerConfigSelector:\n                description: AlertmanagerConfigs to be selected for to merge and configure\n                  Alertmanager with.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              alertmanagerConfiguration:\n                description: 'EXPERIMENTAL: alertmanagerConfiguration specifies the\n                  configuration of Alertmanager. If defined, it takes precedence over\n                  the `configSecret` field. This field may change in future releases.'\n                properties:\n                  global:\n                    description: Defines the global parameters of the Alertmanager\n                      configuration.\n                    properties:\n                      httpConfig:\n                        description: HTTP client configuration.\n                        properties:\n                          authorization:\n                            description: Authorization header configuration for the\n                              client. This is mutually exclusive with BasicAuth and\n                              is only available starting from Alertmanager v0.22+.\n                            properties:\n                              credentials:\n                                description: Selects a key of a Secret in the namespace\n                                  that contains the credentials for authentication.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              type:\n                                description: \"Defines the authentication type. The\n                                  value is case-insensitive. \\n \\\"Basic\\\" is not a\n                                  supported value. \\n Default: \\\"Bearer\\\"\"\n                                type: string\n                            type: object\n                          basicAuth:\n                            description: BasicAuth for the client. This is mutually\n                              exclusive with Authorization. If both are defined, BasicAuth\n                              takes precedence.\n                            properties:\n                              password:\n                                description: '`password` specifies a key of a Secret\n                                  containing the password for authentication.'\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              username:\n                                description: '`username` specifies a key of a Secret\n                                  containing the username for authentication.'\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                          bearerTokenSecret:\n                            description: The secret's key that contains the bearer\n                              token to be used by the client for authentication. The\n                              secret needs to be in the same namespace as the Alertmanager\n                              object and accessible by the Prometheus Operator.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          followRedirects:\n                            description: FollowRedirects specifies whether the client\n                              should follow HTTP 3xx redirects.\n                            type: boolean\n                          oauth2:\n                            description: OAuth2 client credentials used to fetch a\n                              token for the targets.\n                            properties:\n                              clientId:\n                                description: '`clientId` specifies a key of a Secret\n                                  or ConfigMap containing the OAuth2 client''s ID.'\n                                properties:\n                                  configMap:\n                                    description: ConfigMap containing data to use\n                                      for the targets.\n                                    properties:\n                                      key:\n                                        description: The key to select.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the ConfigMap\n                                          or its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  secret:\n                                    description: Secret containing data to use for\n                                      the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              clientSecret:\n                                description: '`clientSecret` specifies a key of a\n                                  Secret containing the OAuth2 client''s secret.'\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              endpointParams:\n                                additionalProperties:\n                                  type: string\n                                description: '`endpointParams` configures the HTTP\n                                  parameters to append to the token URL.'\n                                type: object\n                              scopes:\n                                description: '`scopes` defines the OAuth2 scopes used\n                                  for the token request.'\n                                items:\n                                  type: string\n                                type: array\n                              tokenUrl:\n                                description: '`tokenURL` configures the URL to fetch\n                                  the token from.'\n                                minLength: 1\n                                type: string\n                            required:\n                            - clientId\n                            - clientSecret\n                            - tokenUrl\n                            type: object\n                          proxyURL:\n                            description: Optional proxy URL.\n                            type: string\n                          tlsConfig:\n                            description: TLS configuration for the client.\n                            properties:\n                              ca:\n                                description: Certificate authority used when verifying\n                                  server certificates.\n                                properties:\n                                  configMap:\n                                    description: ConfigMap containing data to use\n                                      for the targets.\n                                    properties:\n                                      key:\n                                        description: The key to select.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the ConfigMap\n                                          or its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  secret:\n                                    description: Secret containing data to use for\n                                      the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              cert:\n                                description: Client certificate to present when doing\n                                  client-authentication.\n                                properties:\n                                  configMap:\n                                    description: ConfigMap containing data to use\n                                      for the targets.\n                                    properties:\n                                      key:\n                                        description: The key to select.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the ConfigMap\n                                          or its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                  secret:\n                                    description: Secret containing data to use for\n                                      the targets.\n                                    properties:\n                                      key:\n                                        description: The key of the secret to select\n                                          from.  Must be a valid secret key.\n                                        type: string\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                      optional:\n                                        description: Specify whether the Secret or\n                                          its key must be defined\n                                        type: boolean\n                                    required:\n                                    - key\n                                    type: object\n                                    x-kubernetes-map-type: atomic\n                                type: object\n                              insecureSkipVerify:\n                                description: Disable target certificate validation.\n                                type: boolean\n                              keySecret:\n                                description: Secret containing the client key file\n                                  for the targets.\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              serverName:\n                                description: Used to verify the hostname for the targets.\n                                type: string\n                            type: object\n                        type: object\n                      opsGenieApiKey:\n                        description: The default OpsGenie API Key.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      opsGenieApiUrl:\n                        description: The default OpsGenie API URL.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      pagerdutyUrl:\n                        description: The default Pagerduty URL.\n                        type: string\n                      resolveTimeout:\n                        description: ResolveTimeout is the default value used by alertmanager\n                          if the alert does not include EndsAt, after this time passes\n                          it can declare the alert as resolved if it has not been\n                          updated. This has no impact on alerts from Prometheus, as\n                          they always include EndsAt.\n                        pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                        type: string\n                      slackApiUrl:\n                        description: The default Slack API URL.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      smtp:\n                        description: Configures global SMTP parameters.\n                        properties:\n                          authIdentity:\n                            description: SMTP Auth using PLAIN\n                            type: string\n                          authPassword:\n                            description: SMTP Auth using LOGIN and PLAIN.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          authSecret:\n                            description: SMTP Auth using CRAM-MD5.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          authUsername:\n                            description: SMTP Auth using CRAM-MD5, LOGIN and PLAIN.\n                              If empty, Alertmanager doesn't authenticate to the SMTP\n                              server.\n                            type: string\n                          from:\n                            description: The default SMTP From header field.\n                            type: string\n                          hello:\n                            description: The default hostname to identify to the SMTP\n                              server.\n                            type: string\n                          requireTLS:\n                            description: The default SMTP TLS requirement. Note that\n                              Go does not support unencrypted connections to remote\n                              SMTP endpoints.\n                            type: boolean\n                          smartHost:\n                            description: The default SMTP smarthost used for sending\n                              emails.\n                            properties:\n                              host:\n                                description: Defines the host's address, it can be\n                                  a DNS name or a literal IP address.\n                                minLength: 1\n                                type: string\n                              port:\n                                description: Defines the host's port, it can be a\n                                  literal port number or a port name.\n                                minLength: 1\n                                type: string\n                            required:\n                            - host\n                            - port\n                            type: object\n                        type: object\n                    type: object\n                  name:\n                    description: The name of the AlertmanagerConfig resource which\n                      is used to generate the Alertmanager configuration. It must\n                      be defined in the same namespace as the Alertmanager object.\n                      The operator will not enforce a `namespace` label for routes\n                      and inhibition rules.\n                    minLength: 1\n                    type: string\n                  templates:\n                    description: Custom notification templates.\n                    items:\n                      description: SecretOrConfigMap allows to specify data as a Secret\n                        or ConfigMap. Fields are mutually exclusive.\n                      properties:\n                        configMap:\n                          description: ConfigMap containing data to use for the targets.\n                          properties:\n                            key:\n                              description: The key to select.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the ConfigMap or its key\n                                must be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        secret:\n                          description: Secret containing data to use for the targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    type: array\n                type: object\n              automountServiceAccountToken:\n                description: 'AutomountServiceAccountToken indicates whether a service\n                  account token should be automatically mounted in the pod. If the\n                  service account has `automountServiceAccountToken: true`, set the\n                  field to `false` to opt out of automounting API credentials.'\n                type: boolean\n              baseImage:\n                description: 'Base image that is used to deploy pods, without tag.\n                  Deprecated: use ''image'' instead.'\n                type: string\n              clusterAdvertiseAddress:\n                description: 'ClusterAdvertiseAddress is the explicit address to advertise\n                  in cluster. Needs to be provided for non RFC1918 [1] (public) addresses.\n                  [1] RFC1918: https://tools.ietf.org/html/rfc1918'\n                type: string\n              clusterGossipInterval:\n                description: Interval between gossip attempts.\n                pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              clusterLabel:\n                description: Defines the identifier that uniquely identifies the Alertmanager\n                  cluster. You should only set it when the Alertmanager cluster includes\n                  Alertmanager instances which are external to this Alertmanager resource.\n                  In practice, the addresses of the external instances are provided\n                  via the `.spec.additionalPeers` field.\n                type: string\n              clusterPeerTimeout:\n                description: Timeout for cluster peering.\n                pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              clusterPushpullInterval:\n                description: Interval between pushpull attempts.\n                pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              configMaps:\n                description: ConfigMaps is a list of ConfigMaps in the same namespace\n                  as the Alertmanager object, which shall be mounted into the Alertmanager\n                  Pods. Each ConfigMap is added to the StatefulSet definition as a\n                  volume named `configmap-<configmap-name>`. The ConfigMaps are mounted\n                  into `/etc/alertmanager/configmaps/<configmap-name>` in the 'alertmanager'\n                  container.\n                items:\n                  type: string\n                type: array\n              configSecret:\n                description: \"ConfigSecret is the name of a Kubernetes Secret in the\n                  same namespace as the Alertmanager object, which contains the configuration\n                  for this Alertmanager instance. If empty, it defaults to `alertmanager-<alertmanager-name>`.\n                  \\n The Alertmanager configuration should be available under the\n                  `alertmanager.yaml` key. Additional keys from the original secret\n                  are copied to the generated secret and mounted into the `/etc/alertmanager/config`\n                  directory in the `alertmanager` container. \\n If either the secret\n                  or the `alertmanager.yaml` key is missing, the operator provisions\n                  a minimal Alertmanager configuration with one empty receiver (effectively\n                  dropping alert notifications).\"\n                type: string\n              containers:\n                description: 'Containers allows injecting additional containers. This\n                  is meant to allow adding an authentication proxy to an Alertmanager\n                  pod. Containers described here modify an operator generated container\n                  if they share the same name and modifications are done via a strategic\n                  merge patch. The current container names are: `alertmanager` and\n                  `config-reloader`. Overriding containers is entirely outside the\n                  scope of what the maintainers will support and by doing so, you\n                  accept that this behaviour may break at any time without notice.'\n                items:\n                  description: A single application container that you want to run\n                    within a pod.\n                  properties:\n                    args:\n                      description: 'Arguments to the entrypoint. The container image''s\n                        CMD is used if this is not provided. Variable references $(VAR_NAME)\n                        are expanded using the container''s environment. If a variable\n                        cannot be resolved, the reference in the input string will\n                        be unchanged. Double $$ are reduced to a single $, which allows\n                        for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\n                        produce the string literal \"$(VAR_NAME)\". Escaped references\n                        will never be expanded, regardless of whether the variable\n                        exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    command:\n                      description: 'Entrypoint array. Not executed within a shell.\n                        The container image''s ENTRYPOINT is used if this is not provided.\n                        Variable references $(VAR_NAME) are expanded using the container''s\n                        environment. If a variable cannot be resolved, the reference\n                        in the input string will be unchanged. Double $$ are reduced\n                        to a single $, which allows for escaping the $(VAR_NAME) syntax:\n                        i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\n                        Escaped references will never be expanded, regardless of whether\n                        the variable exists or not. Cannot be updated. More info:\n                        https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    env:\n                      description: List of environment variables to set in the container.\n                        Cannot be updated.\n                      items:\n                        description: EnvVar represents an environment variable present\n                          in a Container.\n                        properties:\n                          name:\n                            description: Name of the environment variable. Must be\n                              a C_IDENTIFIER.\n                            type: string\n                          value:\n                            description: 'Variable references $(VAR_NAME) are expanded\n                              using the previously defined environment variables in\n                              the container and any service environment variables.\n                              If a variable cannot be resolved, the reference in the\n                              input string will be unchanged. Double $$ are reduced\n                              to a single $, which allows for escaping the $(VAR_NAME)\n                              syntax: i.e. \"$$(VAR_NAME)\" will produce the string\n                              literal \"$(VAR_NAME)\". Escaped references will never\n                              be expanded, regardless of whether the variable exists\n                              or not. Defaults to \"\".'\n                            type: string\n                          valueFrom:\n                            description: Source for the environment variable's value.\n                              Cannot be used if value is not empty.\n                            properties:\n                              configMapKeyRef:\n                                description: Selects a key of a ConfigMap.\n                                properties:\n                                  key:\n                                    description: The key to select.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              fieldRef:\n                                description: 'Selects a field of the pod: supports\n                                  metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`,\n                                  `metadata.annotations[''<KEY>'']`, spec.nodeName,\n                                  spec.serviceAccountName, status.hostIP, status.podIP,\n                                  status.podIPs.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, limits.ephemeral-storage, requests.cpu,\n                                  requests.memory and requests.ephemeral-storage)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              secretKeyRef:\n                                description: Selects a key of a secret in the pod's\n                                  namespace\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    envFrom:\n                      description: List of sources to populate environment variables\n                        in the container. The keys defined within a source must be\n                        a C_IDENTIFIER. All invalid keys will be reported as an event\n                        when the container is starting. When a key exists in multiple\n                        sources, the value associated with the last source will take\n                        precedence. Values defined by an Env with a duplicate key\n                        will take precedence. Cannot be updated.\n                      items:\n                        description: EnvFromSource represents the source of a set\n                          of ConfigMaps\n                        properties:\n                          configMapRef:\n                            description: The ConfigMap to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap must be\n                                  defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          prefix:\n                            description: An optional identifier to prepend to each\n                              key in the ConfigMap. Must be a C_IDENTIFIER.\n                            type: string\n                          secretRef:\n                            description: The Secret to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret must be defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    image:\n                      description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                        This field is optional to allow higher level config management\n                        to default or override container images in workload controllers\n                        like Deployments and StatefulSets.'\n                      type: string\n                    imagePullPolicy:\n                      description: 'Image pull policy. One of Always, Never, IfNotPresent.\n                        Defaults to Always if :latest tag is specified, or IfNotPresent\n                        otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                      type: string\n                    lifecycle:\n                      description: Actions that the management system should take\n                        in response to container lifecycle events. Cannot be updated.\n                      properties:\n                        postStart:\n                          description: 'PostStart is called immediately after a container\n                            is created. If the handler fails, the container is terminated\n                            and restarted according to its restart policy. Other management\n                            of the container blocks until the hook completes. More\n                            info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                        preStop:\n                          description: 'PreStop is called immediately before a container\n                            is terminated due to an API request or management event\n                            such as liveness/startup probe failure, preemption, resource\n                            contention, etc. The handler is not called if the container\n                            crashes or exits. The Pod''s termination grace period\n                            countdown begins before the PreStop hook is executed.\n                            Regardless of the outcome of the handler, the container\n                            will eventually terminate within the Pod''s termination\n                            grace period (unless delayed by finalizers). Other management\n                            of the container blocks until the hook completes or until\n                            the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                      type: object\n                    livenessProbe:\n                      description: 'Periodic probe of container liveness. Container\n                        will be restarted if the probe fails. Cannot be updated. More\n                        info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    name:\n                      description: Name of the container specified as a DNS_LABEL.\n                        Each container in a pod must have a unique name (DNS_LABEL).\n                        Cannot be updated.\n                      type: string\n                    ports:\n                      description: List of ports to expose from the container. Not\n                        specifying a port here DOES NOT prevent that port from being\n                        exposed. Any port which is listening on the default \"0.0.0.0\"\n                        address inside a container will be accessible from the network.\n                        Modifying this array with strategic merge patch may corrupt\n                        the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255.\n                        Cannot be updated.\n                      items:\n                        description: ContainerPort represents a network port in a\n                          single container.\n                        properties:\n                          containerPort:\n                            description: Number of port to expose on the pod's IP\n                              address. This must be a valid port number, 0 < x < 65536.\n                            format: int32\n                            type: integer\n                          hostIP:\n                            description: What host IP to bind the external port to.\n                            type: string\n                          hostPort:\n                            description: Number of port to expose on the host. If\n                              specified, this must be a valid port number, 0 < x <\n                              65536. If HostNetwork is specified, this must match\n                              ContainerPort. Most containers do not need this.\n                            format: int32\n                            type: integer\n                          name:\n                            description: If specified, this must be an IANA_SVC_NAME\n                              and unique within the pod. Each named port in a pod\n                              must have a unique name. Name for the port that can\n                              be referred to by services.\n                            type: string\n                          protocol:\n                            default: TCP\n                            description: Protocol for port. Must be UDP, TCP, or SCTP.\n                              Defaults to \"TCP\".\n                            type: string\n                        required:\n                        - containerPort\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - containerPort\n                      - protocol\n                      x-kubernetes-list-type: map\n                    readinessProbe:\n                      description: 'Periodic probe of container service readiness.\n                        Container will be removed from service endpoints if the probe\n                        fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    resizePolicy:\n                      description: Resources resize policy for the container.\n                      items:\n                        description: ContainerResizePolicy represents resource resize\n                          policy for the container.\n                        properties:\n                          resourceName:\n                            description: 'Name of the resource to which this resource\n                              resize policy applies. Supported values: cpu, memory.'\n                            type: string\n                          restartPolicy:\n                            description: Restart policy to apply when specified resource\n                              is resized. If not specified, it defaults to NotRequired.\n                            type: string\n                        required:\n                        - resourceName\n                        - restartPolicy\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    resources:\n                      description: 'Compute Resources required by this container.\n                        Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                      properties:\n                        claims:\n                          description: \"Claims lists the names of resources, defined\n                            in spec.resourceClaims, that are used by this container.\n                            \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                            feature gate. \\n This field is immutable. It can only\n                            be set for containers.\"\n                          items:\n                            description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                            properties:\n                              name:\n                                description: Name must match the name of one entry\n                                  in pod.spec.resourceClaims of the Pod where this\n                                  field is used. It makes that resource available\n                                  inside a container.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - name\n                          x-kubernetes-list-type: map\n                        limits:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Limits describes the maximum amount of compute\n                            resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                        requests:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Requests describes the minimum amount of compute\n                            resources required. If Requests is omitted for a container,\n                            it defaults to Limits if that is explicitly specified,\n                            otherwise to an implementation-defined value. Requests\n                            cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                      type: object\n                    restartPolicy:\n                      description: 'RestartPolicy defines the restart behavior of\n                        individual containers in a pod. This field may only be set\n                        for init containers, and the only allowed value is \"Always\".\n                        For non-init containers or when this field is not specified,\n                        the restart behavior is defined by the Pod''s restart policy\n                        and the container type. Setting the RestartPolicy as \"Always\"\n                        for the init container will have the following effect: this\n                        init container will be continually restarted on exit until\n                        all regular containers have terminated. Once all regular containers\n                        have completed, all init containers with restartPolicy \"Always\"\n                        will be shut down. This lifecycle differs from normal init\n                        containers and is often referred to as a \"sidecar\" container.\n                        Although this init container still starts in the init container\n                        sequence, it does not wait for the container to complete before\n                        proceeding to the next init container. Instead, the next init\n                        container starts immediately after this init container is\n                        started, or after any startupProbe has successfully completed.'\n                      type: string\n                    securityContext:\n                      description: 'SecurityContext defines the security options the\n                        container should be run with. If set, the fields of SecurityContext\n                        override the equivalent fields of PodSecurityContext. More\n                        info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                      properties:\n                        allowPrivilegeEscalation:\n                          description: 'AllowPrivilegeEscalation controls whether\n                            a process can gain more privileges than its parent process.\n                            This bool directly controls if the no_new_privs flag will\n                            be set on the container process. AllowPrivilegeEscalation\n                            is true always when the container is: 1) run as Privileged\n                            2) has CAP_SYS_ADMIN Note that this field cannot be set\n                            when spec.os.name is windows.'\n                          type: boolean\n                        capabilities:\n                          description: The capabilities to add/drop when running containers.\n                            Defaults to the default set of capabilities granted by\n                            the container runtime. Note that this field cannot be\n                            set when spec.os.name is windows.\n                          properties:\n                            add:\n                              description: Added capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                            drop:\n                              description: Removed capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                          type: object\n                        privileged:\n                          description: Run container in privileged mode. Processes\n                            in privileged containers are essentially equivalent to\n                            root on the host. Defaults to false. Note that this field\n                            cannot be set when spec.os.name is windows.\n                          type: boolean\n                        procMount:\n                          description: procMount denotes the type of proc mount to\n                            use for the containers. The default is DefaultProcMount\n                            which uses the container runtime defaults for readonly\n                            paths and masked paths. This requires the ProcMountType\n                            feature flag to be enabled. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: string\n                        readOnlyRootFilesystem:\n                          description: Whether this container has a read-only root\n                            filesystem. Default is false. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: boolean\n                        runAsGroup:\n                          description: The GID to run the entrypoint of the container\n                            process. Uses runtime default if unset. May also be set\n                            in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        runAsNonRoot:\n                          description: Indicates that the container must run as a\n                            non-root user. If true, the Kubelet will validate the\n                            image at runtime to ensure that it does not run as UID\n                            0 (root) and fail to start the container if it does. If\n                            unset or false, no such validation will be performed.\n                            May also be set in PodSecurityContext.  If set in both\n                            SecurityContext and PodSecurityContext, the value specified\n                            in SecurityContext takes precedence.\n                          type: boolean\n                        runAsUser:\n                          description: The UID to run the entrypoint of the container\n                            process. Defaults to user specified in image metadata\n                            if unspecified. May also be set in PodSecurityContext.  If\n                            set in both SecurityContext and PodSecurityContext, the\n                            value specified in SecurityContext takes precedence. Note\n                            that this field cannot be set when spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        seLinuxOptions:\n                          description: The SELinux context to be applied to the container.\n                            If unspecified, the container runtime will allocate a\n                            random SELinux context for each container.  May also be\n                            set in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          properties:\n                            level:\n                              description: Level is SELinux level label that applies\n                                to the container.\n                              type: string\n                            role:\n                              description: Role is a SELinux role label that applies\n                                to the container.\n                              type: string\n                            type:\n                              description: Type is a SELinux type label that applies\n                                to the container.\n                              type: string\n                            user:\n                              description: User is a SELinux user label that applies\n                                to the container.\n                              type: string\n                          type: object\n                        seccompProfile:\n                          description: The seccomp options to use by this container.\n                            If seccomp options are provided at both the pod & container\n                            level, the container options override the pod options.\n                            Note that this field cannot be set when spec.os.name is\n                            windows.\n                          properties:\n                            localhostProfile:\n                              description: localhostProfile indicates a profile defined\n                                in a file on the node should be used. The profile\n                                must be preconfigured on the node to work. Must be\n                                a descending path, relative to the kubelet's configured\n                                seccomp profile location. Must be set if type is \"Localhost\".\n                                Must NOT be set for any other type.\n                              type: string\n                            type:\n                              description: \"type indicates which kind of seccomp profile\n                                will be applied. Valid options are: \\n Localhost -\n                                a profile defined in a file on the node should be\n                                used. RuntimeDefault - the container runtime default\n                                profile should be used. Unconfined - no profile should\n                                be applied.\"\n                              type: string\n                          required:\n                          - type\n                          type: object\n                        windowsOptions:\n                          description: The Windows specific settings applied to all\n                            containers. If unspecified, the options from the PodSecurityContext\n                            will be used. If set in both SecurityContext and PodSecurityContext,\n                            the value specified in SecurityContext takes precedence.\n                            Note that this field cannot be set when spec.os.name is\n                            linux.\n                          properties:\n                            gmsaCredentialSpec:\n                              description: GMSACredentialSpec is where the GMSA admission\n                                webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                inlines the contents of the GMSA credential spec named\n                                by the GMSACredentialSpecName field.\n                              type: string\n                            gmsaCredentialSpecName:\n                              description: GMSACredentialSpecName is the name of the\n                                GMSA credential spec to use.\n                              type: string\n                            hostProcess:\n                              description: HostProcess determines if a container should\n                                be run as a 'Host Process' container. All of a Pod's\n                                containers must have the same effective HostProcess\n                                value (it is not allowed to have a mix of HostProcess\n                                containers and non-HostProcess containers). In addition,\n                                if HostProcess is true then HostNetwork must also\n                                be set to true.\n                              type: boolean\n                            runAsUserName:\n                              description: The UserName in Windows to run the entrypoint\n                                of the container process. Defaults to the user specified\n                                in image metadata if unspecified. May also be set\n                                in PodSecurityContext. If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence.\n                              type: string\n                          type: object\n                      type: object\n                    startupProbe:\n                      description: 'StartupProbe indicates that the Pod has successfully\n                        initialized. If specified, no other probes are executed until\n                        this completes successfully. If this probe fails, the Pod\n                        will be restarted, just as if the livenessProbe failed. This\n                        can be used to provide different probe parameters at the beginning\n                        of a Pod''s lifecycle, when it might take a long time to load\n                        data or warm a cache, than during steady-state operation.\n                        This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    stdin:\n                      description: Whether this container should allocate a buffer\n                        for stdin in the container runtime. If this is not set, reads\n                        from stdin in the container will always result in EOF. Default\n                        is false.\n                      type: boolean\n                    stdinOnce:\n                      description: Whether the container runtime should close the\n                        stdin channel after it has been opened by a single attach.\n                        When stdin is true the stdin stream will remain open across\n                        multiple attach sessions. If stdinOnce is set to true, stdin\n                        is opened on container start, is empty until the first client\n                        attaches to stdin, and then remains open and accepts data\n                        until the client disconnects, at which time stdin is closed\n                        and remains closed until the container is restarted. If this\n                        flag is false, a container processes that reads from stdin\n                        will never receive an EOF. Default is false\n                      type: boolean\n                    terminationMessagePath:\n                      description: 'Optional: Path at which the file to which the\n                        container''s termination message will be written is mounted\n                        into the container''s filesystem. Message written is intended\n                        to be brief final status, such as an assertion failure message.\n                        Will be truncated by the node if greater than 4096 bytes.\n                        The total message length across all containers will be limited\n                        to 12kb. Defaults to /dev/termination-log. Cannot be updated.'\n                      type: string\n                    terminationMessagePolicy:\n                      description: Indicate how the termination message should be\n                        populated. File will use the contents of terminationMessagePath\n                        to populate the container status message on both success and\n                        failure. FallbackToLogsOnError will use the last chunk of\n                        container log output if the termination message file is empty\n                        and the container exited with an error. The log output is\n                        limited to 2048 bytes or 80 lines, whichever is smaller. Defaults\n                        to File. Cannot be updated.\n                      type: string\n                    tty:\n                      description: Whether this container should allocate a TTY for\n                        itself, also requires 'stdin' to be true. Default is false.\n                      type: boolean\n                    volumeDevices:\n                      description: volumeDevices is the list of block devices to be\n                        used by the container.\n                      items:\n                        description: volumeDevice describes a mapping of a raw block\n                          device within a container.\n                        properties:\n                          devicePath:\n                            description: devicePath is the path inside of the container\n                              that the device will be mapped to.\n                            type: string\n                          name:\n                            description: name must match the name of a persistentVolumeClaim\n                              in the pod\n                            type: string\n                        required:\n                        - devicePath\n                        - name\n                        type: object\n                      type: array\n                    volumeMounts:\n                      description: Pod volumes to mount into the container's filesystem.\n                        Cannot be updated.\n                      items:\n                        description: VolumeMount describes a mounting of a Volume\n                          within a container.\n                        properties:\n                          mountPath:\n                            description: Path within the container at which the volume\n                              should be mounted.  Must not contain ':'.\n                            type: string\n                          mountPropagation:\n                            description: mountPropagation determines how mounts are\n                              propagated from the host to container and the other\n                              way around. When not set, MountPropagationNone is used.\n                              This field is beta in 1.10.\n                            type: string\n                          name:\n                            description: This must match the Name of a Volume.\n                            type: string\n                          readOnly:\n                            description: Mounted read-only if true, read-write otherwise\n                              (false or unspecified). Defaults to false.\n                            type: boolean\n                          subPath:\n                            description: Path within the volume from which the container's\n                              volume should be mounted. Defaults to \"\" (volume's root).\n                            type: string\n                          subPathExpr:\n                            description: Expanded path within the volume from which\n                              the container's volume should be mounted. Behaves similarly\n                              to SubPath but environment variable references $(VAR_NAME)\n                              are expanded using the container's environment. Defaults\n                              to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                              exclusive.\n                            type: string\n                        required:\n                        - mountPath\n                        - name\n                        type: object\n                      type: array\n                    workingDir:\n                      description: Container's working directory. If not specified,\n                        the container runtime's default will be used, which might\n                        be configured in the container image. Cannot be updated.\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              externalUrl:\n                description: The external URL the Alertmanager instances will be available\n                  under. This is necessary to generate correct URLs. This is necessary\n                  if Alertmanager is not served from root of a DNS name.\n                type: string\n              forceEnableClusterMode:\n                description: ForceEnableClusterMode ensures Alertmanager does not\n                  deactivate the cluster mode when running with a single replica.\n                  Use case is e.g. spanning an Alertmanager cluster across Kubernetes\n                  clusters with a single replica in each.\n                type: boolean\n              hostAliases:\n                description: Pods' hostAliases configuration\n                items:\n                  description: HostAlias holds the mapping between IP and hostnames\n                    that will be injected as an entry in the pod's hosts file.\n                  properties:\n                    hostnames:\n                      description: Hostnames for the above IP address.\n                      items:\n                        type: string\n                      type: array\n                    ip:\n                      description: IP address of the host file entry.\n                      type: string\n                  required:\n                  - hostnames\n                  - ip\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - ip\n                x-kubernetes-list-type: map\n              image:\n                description: Image if specified has precedence over baseImage, tag\n                  and sha combinations. Specifying the version is still necessary\n                  to ensure the Prometheus Operator knows what version of Alertmanager\n                  is being configured.\n                type: string\n              imagePullPolicy:\n                description: Image pull policy for the 'alertmanager', 'init-config-reloader'\n                  and 'config-reloader' containers. See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy\n                  for more details.\n                enum:\n                - \"\"\n                - Always\n                - Never\n                - IfNotPresent\n                type: string\n              imagePullSecrets:\n                description: An optional list of references to secrets in the same\n                  namespace to use for pulling prometheus and alertmanager images\n                  from registries see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod\n                items:\n                  description: LocalObjectReference contains enough information to\n                    let you locate the referenced object inside the same namespace.\n                  properties:\n                    name:\n                      description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                        TODO: Add other useful fields. apiVersion, kind, uid?'\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                type: array\n              initContainers:\n                description: 'InitContainers allows adding initContainers to the pod\n                  definition. Those can be used to e.g. fetch secrets for injection\n                  into the Alertmanager configuration from external sources. Any errors\n                  during the execution of an initContainer will lead to a restart\n                  of the Pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/\n                  InitContainers described here modify an operator generated init\n                  containers if they share the same name and modifications are done\n                  via a strategic merge patch. The current init container name is:\n                  `init-config-reloader`. Overriding init containers is entirely outside\n                  the scope of what the maintainers will support and by doing so,\n                  you accept that this behaviour may break at any time without notice.'\n                items:\n                  description: A single application container that you want to run\n                    within a pod.\n                  properties:\n                    args:\n                      description: 'Arguments to the entrypoint. The container image''s\n                        CMD is used if this is not provided. Variable references $(VAR_NAME)\n                        are expanded using the container''s environment. If a variable\n                        cannot be resolved, the reference in the input string will\n                        be unchanged. Double $$ are reduced to a single $, which allows\n                        for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\n                        produce the string literal \"$(VAR_NAME)\". Escaped references\n                        will never be expanded, regardless of whether the variable\n                        exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    command:\n                      description: 'Entrypoint array. Not executed within a shell.\n                        The container image''s ENTRYPOINT is used if this is not provided.\n                        Variable references $(VAR_NAME) are expanded using the container''s\n                        environment. If a variable cannot be resolved, the reference\n                        in the input string will be unchanged. Double $$ are reduced\n                        to a single $, which allows for escaping the $(VAR_NAME) syntax:\n                        i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\n                        Escaped references will never be expanded, regardless of whether\n                        the variable exists or not. Cannot be updated. More info:\n                        https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    env:\n                      description: List of environment variables to set in the container.\n                        Cannot be updated.\n                      items:\n                        description: EnvVar represents an environment variable present\n                          in a Container.\n                        properties:\n                          name:\n                            description: Name of the environment variable. Must be\n                              a C_IDENTIFIER.\n                            type: string\n                          value:\n                            description: 'Variable references $(VAR_NAME) are expanded\n                              using the previously defined environment variables in\n                              the container and any service environment variables.\n                              If a variable cannot be resolved, the reference in the\n                              input string will be unchanged. Double $$ are reduced\n                              to a single $, which allows for escaping the $(VAR_NAME)\n                              syntax: i.e. \"$$(VAR_NAME)\" will produce the string\n                              literal \"$(VAR_NAME)\". Escaped references will never\n                              be expanded, regardless of whether the variable exists\n                              or not. Defaults to \"\".'\n                            type: string\n                          valueFrom:\n                            description: Source for the environment variable's value.\n                              Cannot be used if value is not empty.\n                            properties:\n                              configMapKeyRef:\n                                description: Selects a key of a ConfigMap.\n                                properties:\n                                  key:\n                                    description: The key to select.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              fieldRef:\n                                description: 'Selects a field of the pod: supports\n                                  metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`,\n                                  `metadata.annotations[''<KEY>'']`, spec.nodeName,\n                                  spec.serviceAccountName, status.hostIP, status.podIP,\n                                  status.podIPs.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, limits.ephemeral-storage, requests.cpu,\n                                  requests.memory and requests.ephemeral-storage)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              secretKeyRef:\n                                description: Selects a key of a secret in the pod's\n                                  namespace\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    envFrom:\n                      description: List of sources to populate environment variables\n                        in the container. The keys defined within a source must be\n                        a C_IDENTIFIER. All invalid keys will be reported as an event\n                        when the container is starting. When a key exists in multiple\n                        sources, the value associated with the last source will take\n                        precedence. Values defined by an Env with a duplicate key\n                        will take precedence. Cannot be updated.\n                      items:\n                        description: EnvFromSource represents the source of a set\n                          of ConfigMaps\n                        properties:\n                          configMapRef:\n                            description: The ConfigMap to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap must be\n                                  defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          prefix:\n                            description: An optional identifier to prepend to each\n                              key in the ConfigMap. Must be a C_IDENTIFIER.\n                            type: string\n                          secretRef:\n                            description: The Secret to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret must be defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    image:\n                      description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                        This field is optional to allow higher level config management\n                        to default or override container images in workload controllers\n                        like Deployments and StatefulSets.'\n                      type: string\n                    imagePullPolicy:\n                      description: 'Image pull policy. One of Always, Never, IfNotPresent.\n                        Defaults to Always if :latest tag is specified, or IfNotPresent\n                        otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                      type: string\n                    lifecycle:\n                      description: Actions that the management system should take\n                        in response to container lifecycle events. Cannot be updated.\n                      properties:\n                        postStart:\n                          description: 'PostStart is called immediately after a container\n                            is created. If the handler fails, the container is terminated\n                            and restarted according to its restart policy. Other management\n                            of the container blocks until the hook completes. More\n                            info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                        preStop:\n                          description: 'PreStop is called immediately before a container\n                            is terminated due to an API request or management event\n                            such as liveness/startup probe failure, preemption, resource\n                            contention, etc. The handler is not called if the container\n                            crashes or exits. The Pod''s termination grace period\n                            countdown begins before the PreStop hook is executed.\n                            Regardless of the outcome of the handler, the container\n                            will eventually terminate within the Pod''s termination\n                            grace period (unless delayed by finalizers). Other management\n                            of the container blocks until the hook completes or until\n                            the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                      type: object\n                    livenessProbe:\n                      description: 'Periodic probe of container liveness. Container\n                        will be restarted if the probe fails. Cannot be updated. More\n                        info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    name:\n                      description: Name of the container specified as a DNS_LABEL.\n                        Each container in a pod must have a unique name (DNS_LABEL).\n                        Cannot be updated.\n                      type: string\n                    ports:\n                      description: List of ports to expose from the container. Not\n                        specifying a port here DOES NOT prevent that port from being\n                        exposed. Any port which is listening on the default \"0.0.0.0\"\n                        address inside a container will be accessible from the network.\n                        Modifying this array with strategic merge patch may corrupt\n                        the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255.\n                        Cannot be updated.\n                      items:\n                        description: ContainerPort represents a network port in a\n                          single container.\n                        properties:\n                          containerPort:\n                            description: Number of port to expose on the pod's IP\n                              address. This must be a valid port number, 0 < x < 65536.\n                            format: int32\n                            type: integer\n                          hostIP:\n                            description: What host IP to bind the external port to.\n                            type: string\n                          hostPort:\n                            description: Number of port to expose on the host. If\n                              specified, this must be a valid port number, 0 < x <\n                              65536. If HostNetwork is specified, this must match\n                              ContainerPort. Most containers do not need this.\n                            format: int32\n                            type: integer\n                          name:\n                            description: If specified, this must be an IANA_SVC_NAME\n                              and unique within the pod. Each named port in a pod\n                              must have a unique name. Name for the port that can\n                              be referred to by services.\n                            type: string\n                          protocol:\n                            default: TCP\n                            description: Protocol for port. Must be UDP, TCP, or SCTP.\n                              Defaults to \"TCP\".\n                            type: string\n                        required:\n                        - containerPort\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - containerPort\n                      - protocol\n                      x-kubernetes-list-type: map\n                    readinessProbe:\n                      description: 'Periodic probe of container service readiness.\n                        Container will be removed from service endpoints if the probe\n                        fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    resizePolicy:\n                      description: Resources resize policy for the container.\n                      items:\n                        description: ContainerResizePolicy represents resource resize\n                          policy for the container.\n                        properties:\n                          resourceName:\n                            description: 'Name of the resource to which this resource\n                              resize policy applies. Supported values: cpu, memory.'\n                            type: string\n                          restartPolicy:\n                            description: Restart policy to apply when specified resource\n                              is resized. If not specified, it defaults to NotRequired.\n                            type: string\n                        required:\n                        - resourceName\n                        - restartPolicy\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    resources:\n                      description: 'Compute Resources required by this container.\n                        Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                      properties:\n                        claims:\n                          description: \"Claims lists the names of resources, defined\n                            in spec.resourceClaims, that are used by this container.\n                            \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                            feature gate. \\n This field is immutable. It can only\n                            be set for containers.\"\n                          items:\n                            description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                            properties:\n                              name:\n                                description: Name must match the name of one entry\n                                  in pod.spec.resourceClaims of the Pod where this\n                                  field is used. It makes that resource available\n                                  inside a container.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - name\n                          x-kubernetes-list-type: map\n                        limits:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Limits describes the maximum amount of compute\n                            resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                        requests:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Requests describes the minimum amount of compute\n                            resources required. If Requests is omitted for a container,\n                            it defaults to Limits if that is explicitly specified,\n                            otherwise to an implementation-defined value. Requests\n                            cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                      type: object\n                    restartPolicy:\n                      description: 'RestartPolicy defines the restart behavior of\n                        individual containers in a pod. This field may only be set\n                        for init containers, and the only allowed value is \"Always\".\n                        For non-init containers or when this field is not specified,\n                        the restart behavior is defined by the Pod''s restart policy\n                        and the container type. Setting the RestartPolicy as \"Always\"\n                        for the init container will have the following effect: this\n                        init container will be continually restarted on exit until\n                        all regular containers have terminated. Once all regular containers\n                        have completed, all init containers with restartPolicy \"Always\"\n                        will be shut down. This lifecycle differs from normal init\n                        containers and is often referred to as a \"sidecar\" container.\n                        Although this init container still starts in the init container\n                        sequence, it does not wait for the container to complete before\n                        proceeding to the next init container. Instead, the next init\n                        container starts immediately after this init container is\n                        started, or after any startupProbe has successfully completed.'\n                      type: string\n                    securityContext:\n                      description: 'SecurityContext defines the security options the\n                        container should be run with. If set, the fields of SecurityContext\n                        override the equivalent fields of PodSecurityContext. More\n                        info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                      properties:\n                        allowPrivilegeEscalation:\n                          description: 'AllowPrivilegeEscalation controls whether\n                            a process can gain more privileges than its parent process.\n                            This bool directly controls if the no_new_privs flag will\n                            be set on the container process. AllowPrivilegeEscalation\n                            is true always when the container is: 1) run as Privileged\n                            2) has CAP_SYS_ADMIN Note that this field cannot be set\n                            when spec.os.name is windows.'\n                          type: boolean\n                        capabilities:\n                          description: The capabilities to add/drop when running containers.\n                            Defaults to the default set of capabilities granted by\n                            the container runtime. Note that this field cannot be\n                            set when spec.os.name is windows.\n                          properties:\n                            add:\n                              description: Added capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                            drop:\n                              description: Removed capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                          type: object\n                        privileged:\n                          description: Run container in privileged mode. Processes\n                            in privileged containers are essentially equivalent to\n                            root on the host. Defaults to false. Note that this field\n                            cannot be set when spec.os.name is windows.\n                          type: boolean\n                        procMount:\n                          description: procMount denotes the type of proc mount to\n                            use for the containers. The default is DefaultProcMount\n                            which uses the container runtime defaults for readonly\n                            paths and masked paths. This requires the ProcMountType\n                            feature flag to be enabled. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: string\n                        readOnlyRootFilesystem:\n                          description: Whether this container has a read-only root\n                            filesystem. Default is false. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: boolean\n                        runAsGroup:\n                          description: The GID to run the entrypoint of the container\n                            process. Uses runtime default if unset. May also be set\n                            in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        runAsNonRoot:\n                          description: Indicates that the container must run as a\n                            non-root user. If true, the Kubelet will validate the\n                            image at runtime to ensure that it does not run as UID\n                            0 (root) and fail to start the container if it does. If\n                            unset or false, no such validation will be performed.\n                            May also be set in PodSecurityContext.  If set in both\n                            SecurityContext and PodSecurityContext, the value specified\n                            in SecurityContext takes precedence.\n                          type: boolean\n                        runAsUser:\n                          description: The UID to run the entrypoint of the container\n                            process. Defaults to user specified in image metadata\n                            if unspecified. May also be set in PodSecurityContext.  If\n                            set in both SecurityContext and PodSecurityContext, the\n                            value specified in SecurityContext takes precedence. Note\n                            that this field cannot be set when spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        seLinuxOptions:\n                          description: The SELinux context to be applied to the container.\n                            If unspecified, the container runtime will allocate a\n                            random SELinux context for each container.  May also be\n                            set in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          properties:\n                            level:\n                              description: Level is SELinux level label that applies\n                                to the container.\n                              type: string\n                            role:\n                              description: Role is a SELinux role label that applies\n                                to the container.\n                              type: string\n                            type:\n                              description: Type is a SELinux type label that applies\n                                to the container.\n                              type: string\n                            user:\n                              description: User is a SELinux user label that applies\n                                to the container.\n                              type: string\n                          type: object\n                        seccompProfile:\n                          description: The seccomp options to use by this container.\n                            If seccomp options are provided at both the pod & container\n                            level, the container options override the pod options.\n                            Note that this field cannot be set when spec.os.name is\n                            windows.\n                          properties:\n                            localhostProfile:\n                              description: localhostProfile indicates a profile defined\n                                in a file on the node should be used. The profile\n                                must be preconfigured on the node to work. Must be\n                                a descending path, relative to the kubelet's configured\n                                seccomp profile location. Must be set if type is \"Localhost\".\n                                Must NOT be set for any other type.\n                              type: string\n                            type:\n                              description: \"type indicates which kind of seccomp profile\n                                will be applied. Valid options are: \\n Localhost -\n                                a profile defined in a file on the node should be\n                                used. RuntimeDefault - the container runtime default\n                                profile should be used. Unconfined - no profile should\n                                be applied.\"\n                              type: string\n                          required:\n                          - type\n                          type: object\n                        windowsOptions:\n                          description: The Windows specific settings applied to all\n                            containers. If unspecified, the options from the PodSecurityContext\n                            will be used. If set in both SecurityContext and PodSecurityContext,\n                            the value specified in SecurityContext takes precedence.\n                            Note that this field cannot be set when spec.os.name is\n                            linux.\n                          properties:\n                            gmsaCredentialSpec:\n                              description: GMSACredentialSpec is where the GMSA admission\n                                webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                inlines the contents of the GMSA credential spec named\n                                by the GMSACredentialSpecName field.\n                              type: string\n                            gmsaCredentialSpecName:\n                              description: GMSACredentialSpecName is the name of the\n                                GMSA credential spec to use.\n                              type: string\n                            hostProcess:\n                              description: HostProcess determines if a container should\n                                be run as a 'Host Process' container. All of a Pod's\n                                containers must have the same effective HostProcess\n                                value (it is not allowed to have a mix of HostProcess\n                                containers and non-HostProcess containers). In addition,\n                                if HostProcess is true then HostNetwork must also\n                                be set to true.\n                              type: boolean\n                            runAsUserName:\n                              description: The UserName in Windows to run the entrypoint\n                                of the container process. Defaults to the user specified\n                                in image metadata if unspecified. May also be set\n                                in PodSecurityContext. If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence.\n                              type: string\n                          type: object\n                      type: object\n                    startupProbe:\n                      description: 'StartupProbe indicates that the Pod has successfully\n                        initialized. If specified, no other probes are executed until\n                        this completes successfully. If this probe fails, the Pod\n                        will be restarted, just as if the livenessProbe failed. This\n                        can be used to provide different probe parameters at the beginning\n                        of a Pod''s lifecycle, when it might take a long time to load\n                        data or warm a cache, than during steady-state operation.\n                        This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    stdin:\n                      description: Whether this container should allocate a buffer\n                        for stdin in the container runtime. If this is not set, reads\n                        from stdin in the container will always result in EOF. Default\n                        is false.\n                      type: boolean\n                    stdinOnce:\n                      description: Whether the container runtime should close the\n                        stdin channel after it has been opened by a single attach.\n                        When stdin is true the stdin stream will remain open across\n                        multiple attach sessions. If stdinOnce is set to true, stdin\n                        is opened on container start, is empty until the first client\n                        attaches to stdin, and then remains open and accepts data\n                        until the client disconnects, at which time stdin is closed\n                        and remains closed until the container is restarted. If this\n                        flag is false, a container processes that reads from stdin\n                        will never receive an EOF. Default is false\n                      type: boolean\n                    terminationMessagePath:\n                      description: 'Optional: Path at which the file to which the\n                        container''s termination message will be written is mounted\n                        into the container''s filesystem. Message written is intended\n                        to be brief final status, such as an assertion failure message.\n                        Will be truncated by the node if greater than 4096 bytes.\n                        The total message length across all containers will be limited\n                        to 12kb. Defaults to /dev/termination-log. Cannot be updated.'\n                      type: string\n                    terminationMessagePolicy:\n                      description: Indicate how the termination message should be\n                        populated. File will use the contents of terminationMessagePath\n                        to populate the container status message on both success and\n                        failure. FallbackToLogsOnError will use the last chunk of\n                        container log output if the termination message file is empty\n                        and the container exited with an error. The log output is\n                        limited to 2048 bytes or 80 lines, whichever is smaller. Defaults\n                        to File. Cannot be updated.\n                      type: string\n                    tty:\n                      description: Whether this container should allocate a TTY for\n                        itself, also requires 'stdin' to be true. Default is false.\n                      type: boolean\n                    volumeDevices:\n                      description: volumeDevices is the list of block devices to be\n                        used by the container.\n                      items:\n                        description: volumeDevice describes a mapping of a raw block\n                          device within a container.\n                        properties:\n                          devicePath:\n                            description: devicePath is the path inside of the container\n                              that the device will be mapped to.\n                            type: string\n                          name:\n                            description: name must match the name of a persistentVolumeClaim\n                              in the pod\n                            type: string\n                        required:\n                        - devicePath\n                        - name\n                        type: object\n                      type: array\n                    volumeMounts:\n                      description: Pod volumes to mount into the container's filesystem.\n                        Cannot be updated.\n                      items:\n                        description: VolumeMount describes a mounting of a Volume\n                          within a container.\n                        properties:\n                          mountPath:\n                            description: Path within the container at which the volume\n                              should be mounted.  Must not contain ':'.\n                            type: string\n                          mountPropagation:\n                            description: mountPropagation determines how mounts are\n                              propagated from the host to container and the other\n                              way around. When not set, MountPropagationNone is used.\n                              This field is beta in 1.10.\n                            type: string\n                          name:\n                            description: This must match the Name of a Volume.\n                            type: string\n                          readOnly:\n                            description: Mounted read-only if true, read-write otherwise\n                              (false or unspecified). Defaults to false.\n                            type: boolean\n                          subPath:\n                            description: Path within the volume from which the container's\n                              volume should be mounted. Defaults to \"\" (volume's root).\n                            type: string\n                          subPathExpr:\n                            description: Expanded path within the volume from which\n                              the container's volume should be mounted. Behaves similarly\n                              to SubPath but environment variable references $(VAR_NAME)\n                              are expanded using the container's environment. Defaults\n                              to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                              exclusive.\n                            type: string\n                        required:\n                        - mountPath\n                        - name\n                        type: object\n                      type: array\n                    workingDir:\n                      description: Container's working directory. If not specified,\n                        the container runtime's default will be used, which might\n                        be configured in the container image. Cannot be updated.\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              listenLocal:\n                description: ListenLocal makes the Alertmanager server listen on loopback,\n                  so that it does not bind against the Pod IP. Note this is only for\n                  the Alertmanager UI, not the gossip communication.\n                type: boolean\n              logFormat:\n                description: Log format for Alertmanager to be configured with.\n                enum:\n                - \"\"\n                - logfmt\n                - json\n                type: string\n              logLevel:\n                description: Log level for Alertmanager to be configured with.\n                enum:\n                - \"\"\n                - debug\n                - info\n                - warn\n                - error\n                type: string\n              minReadySeconds:\n                description: Minimum number of seconds for which a newly created pod\n                  should be ready without any of its container crashing for it to\n                  be considered available. Defaults to 0 (pod will be considered available\n                  as soon as it is ready) This is an alpha field from kubernetes 1.22\n                  until 1.24 which requires enabling the StatefulSetMinReadySeconds\n                  feature gate.\n                format: int32\n                type: integer\n              nodeSelector:\n                additionalProperties:\n                  type: string\n                description: Define which Nodes the Pods are scheduled on.\n                type: object\n              paused:\n                description: If set to true all actions on the underlying managed\n                  objects are not goint to be performed, except for delete actions.\n                type: boolean\n              podMetadata:\n                description: \"PodMetadata configures labels and annotations which\n                  are propagated to the Alertmanager pods. \\n The following items\n                  are reserved and cannot be overridden: * \\\"alertmanager\\\" label,\n                  set to the name of the Alertmanager instance. * \\\"app.kubernetes.io/instance\\\"\n                  label, set to the name of the Alertmanager instance. * \\\"app.kubernetes.io/managed-by\\\"\n                  label, set to \\\"prometheus-operator\\\". * \\\"app.kubernetes.io/name\\\"\n                  label, set to \\\"alertmanager\\\". * \\\"app.kubernetes.io/version\\\"\n                  label, set to the Alertmanager version. * \\\"kubectl.kubernetes.io/default-container\\\"\n                  annotation, set to \\\"alertmanager\\\".\"\n                properties:\n                  annotations:\n                    additionalProperties:\n                      type: string\n                    description: 'Annotations is an unstructured key value map stored\n                      with a resource that may be set by external tools to store and\n                      retrieve arbitrary metadata. They are not queryable and should\n                      be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'\n                    type: object\n                  labels:\n                    additionalProperties:\n                      type: string\n                    description: 'Map of string keys and values that can be used to\n                      organize and categorize (scope and select) objects. May match\n                      selectors of replication controllers and services. More info:\n                      http://kubernetes.io/docs/user-guide/labels'\n                    type: object\n                  name:\n                    description: 'Name must be unique within a namespace. Is required\n                      when creating resources, although some resources may allow a\n                      client to request the generation of an appropriate name automatically.\n                      Name is primarily intended for creation idempotence and configuration\n                      definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'\n                    type: string\n                type: object\n              portName:\n                default: web\n                description: Port name used for the pods and governing service. Defaults\n                  to `web`.\n                type: string\n              priorityClassName:\n                description: Priority class assigned to the Pods\n                type: string\n              replicas:\n                description: Size is the expected size of the alertmanager cluster.\n                  The controller will eventually make the size of the running cluster\n                  equal to the expected size.\n                format: int32\n                type: integer\n              resources:\n                description: Define resources requests and limits for single Pods.\n                properties:\n                  claims:\n                    description: \"Claims lists the names of resources, defined in\n                      spec.resourceClaims, that are used by this container. \\n This\n                      is an alpha field and requires enabling the DynamicResourceAllocation\n                      feature gate. \\n This field is immutable. It can only be set\n                      for containers.\"\n                    items:\n                      description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                      properties:\n                        name:\n                          description: Name must match the name of one entry in pod.spec.resourceClaims\n                            of the Pod where this field is used. It makes that resource\n                            available inside a container.\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    type: array\n                    x-kubernetes-list-map-keys:\n                    - name\n                    x-kubernetes-list-type: map\n                  limits:\n                    additionalProperties:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                      x-kubernetes-int-or-string: true\n                    description: 'Limits describes the maximum amount of compute resources\n                      allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                    type: object\n                  requests:\n                    additionalProperties:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                      x-kubernetes-int-or-string: true\n                    description: 'Requests describes the minimum amount of compute\n                      resources required. If Requests is omitted for a container,\n                      it defaults to Limits if that is explicitly specified, otherwise\n                      to an implementation-defined value. Requests cannot exceed Limits.\n                      More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                    type: object\n                type: object\n              retention:\n                default: 120h\n                description: Time duration Alertmanager shall retain data for. Default\n                  is '120h', and must match the regular expression `[0-9]+(ms|s|m|h)`\n                  (milliseconds seconds minutes hours).\n                pattern: ^(0|(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              routePrefix:\n                description: The route prefix Alertmanager registers HTTP handlers\n                  for. This is useful, if using ExternalURL and a proxy is rewriting\n                  HTTP routes of a request, and the actual ExternalURL is still true,\n                  but the server serves requests under a different route prefix. For\n                  example for use with `kubectl proxy`.\n                type: string\n              secrets:\n                description: Secrets is a list of Secrets in the same namespace as\n                  the Alertmanager object, which shall be mounted into the Alertmanager\n                  Pods. Each Secret is added to the StatefulSet definition as a volume\n                  named `secret-<secret-name>`. The Secrets are mounted into `/etc/alertmanager/secrets/<secret-name>`\n                  in the 'alertmanager' container.\n                items:\n                  type: string\n                type: array\n              securityContext:\n                description: SecurityContext holds pod-level security attributes and\n                  common container settings. This defaults to the default PodSecurityContext.\n                properties:\n                  fsGroup:\n                    description: \"A special supplemental group that applies to all\n                      containers in a pod. Some volume types allow the Kubelet to\n                      change the ownership of that volume to be owned by the pod:\n                      \\n 1. The owning GID will be the FSGroup 2. The setgid bit is\n                      set (new files created in the volume will be owned by FSGroup)\n                      3. The permission bits are OR'd with rw-rw---- \\n If unset,\n                      the Kubelet will not modify the ownership and permissions of\n                      any volume. Note that this field cannot be set when spec.os.name\n                      is windows.\"\n                    format: int64\n                    type: integer\n                  fsGroupChangePolicy:\n                    description: 'fsGroupChangePolicy defines behavior of changing\n                      ownership and permission of the volume before being exposed\n                      inside Pod. This field will only apply to volume types which\n                      support fsGroup based ownership(and permissions). It will have\n                      no effect on ephemeral volume types such as: secret, configmaps\n                      and emptydir. Valid values are \"OnRootMismatch\" and \"Always\".\n                      If not specified, \"Always\" is used. Note that this field cannot\n                      be set when spec.os.name is windows.'\n                    type: string\n                  runAsGroup:\n                    description: The GID to run the entrypoint of the container process.\n                      Uses runtime default if unset. May also be set in SecurityContext.  If\n                      set in both SecurityContext and PodSecurityContext, the value\n                      specified in SecurityContext takes precedence for that container.\n                      Note that this field cannot be set when spec.os.name is windows.\n                    format: int64\n                    type: integer\n                  runAsNonRoot:\n                    description: Indicates that the container must run as a non-root\n                      user. If true, the Kubelet will validate the image at runtime\n                      to ensure that it does not run as UID 0 (root) and fail to start\n                      the container if it does. If unset or false, no such validation\n                      will be performed. May also be set in SecurityContext.  If set\n                      in both SecurityContext and PodSecurityContext, the value specified\n                      in SecurityContext takes precedence.\n                    type: boolean\n                  runAsUser:\n                    description: The UID to run the entrypoint of the container process.\n                      Defaults to user specified in image metadata if unspecified.\n                      May also be set in SecurityContext.  If set in both SecurityContext\n                      and PodSecurityContext, the value specified in SecurityContext\n                      takes precedence for that container. Note that this field cannot\n                      be set when spec.os.name is windows.\n                    format: int64\n                    type: integer\n                  seLinuxOptions:\n                    description: The SELinux context to be applied to all containers.\n                      If unspecified, the container runtime will allocate a random\n                      SELinux context for each container.  May also be set in SecurityContext.  If\n                      set in both SecurityContext and PodSecurityContext, the value\n                      specified in SecurityContext takes precedence for that container.\n                      Note that this field cannot be set when spec.os.name is windows.\n                    properties:\n                      level:\n                        description: Level is SELinux level label that applies to\n                          the container.\n                        type: string\n                      role:\n                        description: Role is a SELinux role label that applies to\n                          the container.\n                        type: string\n                      type:\n                        description: Type is a SELinux type label that applies to\n                          the container.\n                        type: string\n                      user:\n                        description: User is a SELinux user label that applies to\n                          the container.\n                        type: string\n                    type: object\n                  seccompProfile:\n                    description: The seccomp options to use by the containers in this\n                      pod. Note that this field cannot be set when spec.os.name is\n                      windows.\n                    properties:\n                      localhostProfile:\n                        description: localhostProfile indicates a profile defined\n                          in a file on the node should be used. The profile must be\n                          preconfigured on the node to work. Must be a descending\n                          path, relative to the kubelet's configured seccomp profile\n                          location. Must be set if type is \"Localhost\". Must NOT be\n                          set for any other type.\n                        type: string\n                      type:\n                        description: \"type indicates which kind of seccomp profile\n                          will be applied. Valid options are: \\n Localhost - a profile\n                          defined in a file on the node should be used. RuntimeDefault\n                          - the container runtime default profile should be used.\n                          Unconfined - no profile should be applied.\"\n                        type: string\n                    required:\n                    - type\n                    type: object\n                  supplementalGroups:\n                    description: A list of groups applied to the first process run\n                      in each container, in addition to the container's primary GID,\n                      the fsGroup (if specified), and group memberships defined in\n                      the container image for the uid of the container process. If\n                      unspecified, no additional groups are added to any container.\n                      Note that group memberships defined in the container image for\n                      the uid of the container process are still effective, even if\n                      they are not included in this list. Note that this field cannot\n                      be set when spec.os.name is windows.\n                    items:\n                      format: int64\n                      type: integer\n                    type: array\n                  sysctls:\n                    description: Sysctls hold a list of namespaced sysctls used for\n                      the pod. Pods with unsupported sysctls (by the container runtime)\n                      might fail to launch. Note that this field cannot be set when\n                      spec.os.name is windows.\n                    items:\n                      description: Sysctl defines a kernel parameter to be set\n                      properties:\n                        name:\n                          description: Name of a property to set\n                          type: string\n                        value:\n                          description: Value of a property to set\n                          type: string\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  windowsOptions:\n                    description: The Windows specific settings applied to all containers.\n                      If unspecified, the options within a container's SecurityContext\n                      will be used. If set in both SecurityContext and PodSecurityContext,\n                      the value specified in SecurityContext takes precedence. Note\n                      that this field cannot be set when spec.os.name is linux.\n                    properties:\n                      gmsaCredentialSpec:\n                        description: GMSACredentialSpec is where the GMSA admission\n                          webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                          inlines the contents of the GMSA credential spec named by\n                          the GMSACredentialSpecName field.\n                        type: string\n                      gmsaCredentialSpecName:\n                        description: GMSACredentialSpecName is the name of the GMSA\n                          credential spec to use.\n                        type: string\n                      hostProcess:\n                        description: HostProcess determines if a container should\n                          be run as a 'Host Process' container. All of a Pod's containers\n                          must have the same effective HostProcess value (it is not\n                          allowed to have a mix of HostProcess containers and non-HostProcess\n                          containers). In addition, if HostProcess is true then HostNetwork\n                          must also be set to true.\n                        type: boolean\n                      runAsUserName:\n                        description: The UserName in Windows to run the entrypoint\n                          of the container process. Defaults to the user specified\n                          in image metadata if unspecified. May also be set in PodSecurityContext.\n                          If set in both SecurityContext and PodSecurityContext, the\n                          value specified in SecurityContext takes precedence.\n                        type: string\n                    type: object\n                type: object\n              serviceAccountName:\n                description: ServiceAccountName is the name of the ServiceAccount\n                  to use to run the Prometheus Pods.\n                type: string\n              sha:\n                description: 'SHA of Alertmanager container image to be deployed.\n                  Defaults to the value of `version`. Similar to a tag, but the SHA\n                  explicitly deploys an immutable container image. Version and Tag\n                  are ignored if SHA is set. Deprecated: use ''image'' instead. The\n                  image digest can be specified as part of the image URL.'\n                type: string\n              storage:\n                description: Storage is the definition of how storage will be used\n                  by the Alertmanager instances.\n                properties:\n                  disableMountSubPath:\n                    description: 'Deprecated: subPath usage will be removed in a future\n                      release.'\n                    type: boolean\n                  emptyDir:\n                    description: 'EmptyDirVolumeSource to be used by the StatefulSet.\n                      If specified, it takes precedence over `ephemeral` and `volumeClaimTemplate`.\n                      More info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir'\n                    properties:\n                      medium:\n                        description: 'medium represents what type of storage medium\n                          should back this directory. The default is \"\" which means\n                          to use the node''s default medium. Must be an empty string\n                          (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                        type: string\n                      sizeLimit:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: 'sizeLimit is the total amount of local storage\n                          required for this EmptyDir volume. The size limit is also\n                          applicable for memory medium. The maximum usage on memory\n                          medium EmptyDir would be the minimum value between the SizeLimit\n                          specified here and the sum of memory limits of all containers\n                          in a pod. The default is nil which means that the limit\n                          is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                    type: object\n                  ephemeral:\n                    description: 'EphemeralVolumeSource to be used by the StatefulSet.\n                      This is a beta field in k8s 1.21 and GA in 1.15. For lower versions,\n                      starting with k8s 1.19, it requires enabling the GenericEphemeralVolume\n                      feature gate. More info: https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes'\n                    properties:\n                      volumeClaimTemplate:\n                        description: \"Will be used to create a stand-alone PVC to\n                          provision the volume. The pod in which this EphemeralVolumeSource\n                          is embedded will be the owner of the PVC, i.e. the PVC will\n                          be deleted together with the pod.  The name of the PVC will\n                          be `<pod name>-<volume name>` where `<volume name>` is the\n                          name from the `PodSpec.Volumes` array entry. Pod validation\n                          will reject the pod if the concatenated name is not valid\n                          for a PVC (for example, too long). \\n An existing PVC with\n                          that name that is not owned by the pod will *not* be used\n                          for the pod to avoid using an unrelated volume by mistake.\n                          Starting the pod is then blocked until the unrelated PVC\n                          is removed. If such a pre-created PVC is meant to be used\n                          by the pod, the PVC has to updated with an owner reference\n                          to the pod once the pod exists. Normally this should not\n                          be necessary, but it may be useful when manually reconstructing\n                          a broken cluster. \\n This field is read-only and no changes\n                          will be made by Kubernetes to the PVC after it has been\n                          created. \\n Required, must not be nil.\"\n                        properties:\n                          metadata:\n                            description: May contain labels and annotations that will\n                              be copied into the PVC when creating it. No other fields\n                              are allowed and will be rejected during validation.\n                            type: object\n                          spec:\n                            description: The specification for the PersistentVolumeClaim.\n                              The entire content is copied unchanged into the PVC\n                              that gets created from this template. The same fields\n                              as in a PersistentVolumeClaim are also valid here.\n                            properties:\n                              accessModes:\n                                description: 'accessModes contains the desired access\n                                  modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                                items:\n                                  type: string\n                                type: array\n                              dataSource:\n                                description: 'dataSource field can be used to specify\n                                  either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                                  * An existing PVC (PersistentVolumeClaim) If the\n                                  provisioner or an external controller can support\n                                  the specified data source, it will create a new\n                                  volume based on the contents of the specified data\n                                  source. When the AnyVolumeDataSource feature gate\n                                  is enabled, dataSource contents will be copied to\n                                  dataSourceRef, and dataSourceRef contents will be\n                                  copied to dataSource when dataSourceRef.namespace\n                                  is not specified. If the namespace is specified,\n                                  then dataSourceRef will not be copied to dataSource.'\n                                properties:\n                                  apiGroup:\n                                    description: APIGroup is the group for the resource\n                                      being referenced. If APIGroup is not specified,\n                                      the specified Kind must be in the core API group.\n                                      For any other third-party types, APIGroup is\n                                      required.\n                                    type: string\n                                  kind:\n                                    description: Kind is the type of resource being\n                                      referenced\n                                    type: string\n                                  name:\n                                    description: Name is the name of resource being\n                                      referenced\n                                    type: string\n                                required:\n                                - kind\n                                - name\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              dataSourceRef:\n                                description: 'dataSourceRef specifies the object from\n                                  which to populate the volume with data, if a non-empty\n                                  volume is desired. This may be any object from a\n                                  non-empty API group (non core object) or a PersistentVolumeClaim\n                                  object. When this field is specified, volume binding\n                                  will only succeed if the type of the specified object\n                                  matches some installed volume populator or dynamic\n                                  provisioner. This field will replace the functionality\n                                  of the dataSource field and as such if both fields\n                                  are non-empty, they must have the same value. For\n                                  backwards compatibility, when namespace isn''t specified\n                                  in dataSourceRef, both fields (dataSource and dataSourceRef)\n                                  will be set to the same value automatically if one\n                                  of them is empty and the other is non-empty. When\n                                  namespace is specified in dataSourceRef, dataSource\n                                  isn''t set to the same value and must be empty.\n                                  There are three important differences between dataSource\n                                  and dataSourceRef: * While dataSource only allows\n                                  two specific types of objects, dataSourceRef allows\n                                  any non-core object, as well as PersistentVolumeClaim\n                                  objects. * While dataSource ignores disallowed values\n                                  (dropping them), dataSourceRef preserves all values,\n                                  and generates an error if a disallowed value is\n                                  specified. * While dataSource only allows local\n                                  objects, dataSourceRef allows objects in any namespaces.\n                                  (Beta) Using this field requires the AnyVolumeDataSource\n                                  feature gate to be enabled. (Alpha) Using the namespace\n                                  field of dataSourceRef requires the CrossNamespaceVolumeDataSource\n                                  feature gate to be enabled.'\n                                properties:\n                                  apiGroup:\n                                    description: APIGroup is the group for the resource\n                                      being referenced. If APIGroup is not specified,\n                                      the specified Kind must be in the core API group.\n                                      For any other third-party types, APIGroup is\n                                      required.\n                                    type: string\n                                  kind:\n                                    description: Kind is the type of resource being\n                                      referenced\n                                    type: string\n                                  name:\n                                    description: Name is the name of resource being\n                                      referenced\n                                    type: string\n                                  namespace:\n                                    description: Namespace is the namespace of resource\n                                      being referenced Note that when a namespace\n                                      is specified, a gateway.networking.k8s.io/ReferenceGrant\n                                      object is required in the referent namespace\n                                      to allow that namespace's owner to accept the\n                                      reference. See the ReferenceGrant documentation\n                                      for details. (Alpha) This field requires the\n                                      CrossNamespaceVolumeDataSource feature gate\n                                      to be enabled.\n                                    type: string\n                                required:\n                                - kind\n                                - name\n                                type: object\n                              resources:\n                                description: 'resources represents the minimum resources\n                                  the volume should have. If RecoverVolumeExpansionFailure\n                                  feature is enabled users are allowed to specify\n                                  resource requirements that are lower than previous\n                                  value but must still be higher than capacity recorded\n                                  in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                                properties:\n                                  claims:\n                                    description: \"Claims lists the names of resources,\n                                      defined in spec.resourceClaims, that are used\n                                      by this container. \\n This is an alpha field\n                                      and requires enabling the DynamicResourceAllocation\n                                      feature gate. \\n This field is immutable. It\n                                      can only be set for containers.\"\n                                    items:\n                                      description: ResourceClaim references one entry\n                                        in PodSpec.ResourceClaims.\n                                      properties:\n                                        name:\n                                          description: Name must match the name of\n                                            one entry in pod.spec.resourceClaims of\n                                            the Pod where this field is used. It makes\n                                            that resource available inside a container.\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Limits describes the maximum amount\n                                      of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Requests describes the minimum amount\n                                      of compute resources required. If Requests is\n                                      omitted for a container, it defaults to Limits\n                                      if that is explicitly specified, otherwise to\n                                      an implementation-defined value. Requests cannot\n                                      exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                    type: object\n                                type: object\n                              selector:\n                                description: selector is a label query over volumes\n                                  to consider for binding.\n                                properties:\n                                  matchExpressions:\n                                    description: matchExpressions is a list of label\n                                      selector requirements. The requirements are\n                                      ANDed.\n                                    items:\n                                      description: A label selector requirement is\n                                        a selector that contains values, a key, and\n                                        an operator that relates the key and values.\n                                      properties:\n                                        key:\n                                          description: key is the label key that the\n                                            selector applies to.\n                                          type: string\n                                        operator:\n                                          description: operator represents a key's\n                                            relationship to a set of values. Valid\n                                            operators are In, NotIn, Exists and DoesNotExist.\n                                          type: string\n                                        values:\n                                          description: values is an array of string\n                                            values. If the operator is In or NotIn,\n                                            the values array must be non-empty. If\n                                            the operator is Exists or DoesNotExist,\n                                            the values array must be empty. This array\n                                            is replaced during a strategic merge patch.\n                                          items:\n                                            type: string\n                                          type: array\n                                      required:\n                                      - key\n                                      - operator\n                                      type: object\n                                    type: array\n                                  matchLabels:\n                                    additionalProperties:\n                                      type: string\n                                    description: matchLabels is a map of {key,value}\n                                      pairs. A single {key,value} in the matchLabels\n                                      map is equivalent to an element of matchExpressions,\n                                      whose key field is \"key\", the operator is \"In\",\n                                      and the values array contains only \"value\".\n                                      The requirements are ANDed.\n                                    type: object\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              storageClassName:\n                                description: 'storageClassName is the name of the\n                                  StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                                type: string\n                              volumeMode:\n                                description: volumeMode defines what type of volume\n                                  is required by the claim. Value of Filesystem is\n                                  implied when not included in claim spec.\n                                type: string\n                              volumeName:\n                                description: volumeName is the binding reference to\n                                  the PersistentVolume backing this claim.\n                                type: string\n                            type: object\n                        required:\n                        - spec\n                        type: object\n                    type: object\n                  volumeClaimTemplate:\n                    description: Defines the PVC spec to be used by the Prometheus\n                      StatefulSets. The easiest way to use a volume that cannot be\n                      automatically provisioned is to use a label selector alongside\n                      manually created PersistentVolumes.\n                    properties:\n                      apiVersion:\n                        description: 'APIVersion defines the versioned schema of this\n                          representation of an object. Servers should convert recognized\n                          schemas to the latest internal value, and may reject unrecognized\n                          values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n                        type: string\n                      kind:\n                        description: 'Kind is a string value representing the REST\n                          resource this object represents. Servers may infer this\n                          from the endpoint the client submits requests to. Cannot\n                          be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n                        type: string\n                      metadata:\n                        description: EmbeddedMetadata contains metadata relevant to\n                          an EmbeddedResource.\n                        properties:\n                          annotations:\n                            additionalProperties:\n                              type: string\n                            description: 'Annotations is an unstructured key value\n                              map stored with a resource that may be set by external\n                              tools to store and retrieve arbitrary metadata. They\n                              are not queryable and should be preserved when modifying\n                              objects. More info: http://kubernetes.io/docs/user-guide/annotations'\n                            type: object\n                          labels:\n                            additionalProperties:\n                              type: string\n                            description: 'Map of string keys and values that can be\n                              used to organize and categorize (scope and select) objects.\n                              May match selectors of replication controllers and services.\n                              More info: http://kubernetes.io/docs/user-guide/labels'\n                            type: object\n                          name:\n                            description: 'Name must be unique within a namespace.\n                              Is required when creating resources, although some resources\n                              may allow a client to request the generation of an appropriate\n                              name automatically. Name is primarily intended for creation\n                              idempotence and configuration definition. Cannot be\n                              updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'\n                            type: string\n                        type: object\n                      spec:\n                        description: 'Defines the desired characteristics of a volume\n                          requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                        properties:\n                          accessModes:\n                            description: 'accessModes contains the desired access\n                              modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                            items:\n                              type: string\n                            type: array\n                          dataSource:\n                            description: 'dataSource field can be used to specify\n                              either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                              * An existing PVC (PersistentVolumeClaim) If the provisioner\n                              or an external controller can support the specified\n                              data source, it will create a new volume based on the\n                              contents of the specified data source. When the AnyVolumeDataSource\n                              feature gate is enabled, dataSource contents will be\n                              copied to dataSourceRef, and dataSourceRef contents\n                              will be copied to dataSource when dataSourceRef.namespace\n                              is not specified. If the namespace is specified, then\n                              dataSourceRef will not be copied to dataSource.'\n                            properties:\n                              apiGroup:\n                                description: APIGroup is the group for the resource\n                                  being referenced. If APIGroup is not specified,\n                                  the specified Kind must be in the core API group.\n                                  For any other third-party types, APIGroup is required.\n                                type: string\n                              kind:\n                                description: Kind is the type of resource being referenced\n                                type: string\n                              name:\n                                description: Name is the name of resource being referenced\n                                type: string\n                            required:\n                            - kind\n                            - name\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          dataSourceRef:\n                            description: 'dataSourceRef specifies the object from\n                              which to populate the volume with data, if a non-empty\n                              volume is desired. This may be any object from a non-empty\n                              API group (non core object) or a PersistentVolumeClaim\n                              object. When this field is specified, volume binding\n                              will only succeed if the type of the specified object\n                              matches some installed volume populator or dynamic provisioner.\n                              This field will replace the functionality of the dataSource\n                              field and as such if both fields are non-empty, they\n                              must have the same value. For backwards compatibility,\n                              when namespace isn''t specified in dataSourceRef, both\n                              fields (dataSource and dataSourceRef) will be set to\n                              the same value automatically if one of them is empty\n                              and the other is non-empty. When namespace is specified\n                              in dataSourceRef, dataSource isn''t set to the same\n                              value and must be empty. There are three important differences\n                              between dataSource and dataSourceRef: * While dataSource\n                              only allows two specific types of objects, dataSourceRef\n                              allows any non-core object, as well as PersistentVolumeClaim\n                              objects. * While dataSource ignores disallowed values\n                              (dropping them), dataSourceRef preserves all values,\n                              and generates an error if a disallowed value is specified.\n                              * While dataSource only allows local objects, dataSourceRef\n                              allows objects in any namespaces. (Beta) Using this\n                              field requires the AnyVolumeDataSource feature gate\n                              to be enabled. (Alpha) Using the namespace field of\n                              dataSourceRef requires the CrossNamespaceVolumeDataSource\n                              feature gate to be enabled.'\n                            properties:\n                              apiGroup:\n                                description: APIGroup is the group for the resource\n                                  being referenced. If APIGroup is not specified,\n                                  the specified Kind must be in the core API group.\n                                  For any other third-party types, APIGroup is required.\n                                type: string\n                              kind:\n                                description: Kind is the type of resource being referenced\n                                type: string\n                              name:\n                                description: Name is the name of resource being referenced\n                                type: string\n                              namespace:\n                                description: Namespace is the namespace of resource\n                                  being referenced Note that when a namespace is specified,\n                                  a gateway.networking.k8s.io/ReferenceGrant object\n                                  is required in the referent namespace to allow that\n                                  namespace's owner to accept the reference. See the\n                                  ReferenceGrant documentation for details. (Alpha)\n                                  This field requires the CrossNamespaceVolumeDataSource\n                                  feature gate to be enabled.\n                                type: string\n                            required:\n                            - kind\n                            - name\n                            type: object\n                          resources:\n                            description: 'resources represents the minimum resources\n                              the volume should have. If RecoverVolumeExpansionFailure\n                              feature is enabled users are allowed to specify resource\n                              requirements that are lower than previous value but\n                              must still be higher than capacity recorded in the status\n                              field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                            properties:\n                              claims:\n                                description: \"Claims lists the names of resources,\n                                  defined in spec.resourceClaims, that are used by\n                                  this container. \\n This is an alpha field and requires\n                                  enabling the DynamicResourceAllocation feature gate.\n                                  \\n This field is immutable. It can only be set for\n                                  containers.\"\n                                items:\n                                  description: ResourceClaim references one entry\n                                    in PodSpec.ResourceClaims.\n                                  properties:\n                                    name:\n                                      description: Name must match the name of one\n                                        entry in pod.spec.resourceClaims of the Pod\n                                        where this field is used. It makes that resource\n                                        available inside a container.\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              limits:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                description: 'Limits describes the maximum amount\n                                  of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                type: object\n                              requests:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                description: 'Requests describes the minimum amount\n                                  of compute resources required. If Requests is omitted\n                                  for a container, it defaults to Limits if that is\n                                  explicitly specified, otherwise to an implementation-defined\n                                  value. Requests cannot exceed Limits. More info:\n                                  https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                type: object\n                            type: object\n                          selector:\n                            description: selector is a label query over volumes to\n                              consider for binding.\n                            properties:\n                              matchExpressions:\n                                description: matchExpressions is a list of label selector\n                                  requirements. The requirements are ANDed.\n                                items:\n                                  description: A label selector requirement is a selector\n                                    that contains values, a key, and an operator that\n                                    relates the key and values.\n                                  properties:\n                                    key:\n                                      description: key is the label key that the selector\n                                        applies to.\n                                      type: string\n                                    operator:\n                                      description: operator represents a key's relationship\n                                        to a set of values. Valid operators are In,\n                                        NotIn, Exists and DoesNotExist.\n                                      type: string\n                                    values:\n                                      description: values is an array of string values.\n                                        If the operator is In or NotIn, the values\n                                        array must be non-empty. If the operator is\n                                        Exists or DoesNotExist, the values array must\n                                        be empty. This array is replaced during a\n                                        strategic merge patch.\n                                      items:\n                                        type: string\n                                      type: array\n                                  required:\n                                  - key\n                                  - operator\n                                  type: object\n                                type: array\n                              matchLabels:\n                                additionalProperties:\n                                  type: string\n                                description: matchLabels is a map of {key,value} pairs.\n                                  A single {key,value} in the matchLabels map is equivalent\n                                  to an element of matchExpressions, whose key field\n                                  is \"key\", the operator is \"In\", and the values array\n                                  contains only \"value\". The requirements are ANDed.\n                                type: object\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          storageClassName:\n                            description: 'storageClassName is the name of the StorageClass\n                              required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                            type: string\n                          volumeMode:\n                            description: volumeMode defines what type of volume is\n                              required by the claim. Value of Filesystem is implied\n                              when not included in claim spec.\n                            type: string\n                          volumeName:\n                            description: volumeName is the binding reference to the\n                              PersistentVolume backing this claim.\n                            type: string\n                        type: object\n                      status:\n                        description: 'Deprecated: this field is never set.'\n                        properties:\n                          accessModes:\n                            description: 'accessModes contains the actual access modes\n                              the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                            items:\n                              type: string\n                            type: array\n                          allocatedResourceStatuses:\n                            additionalProperties:\n                              description: When a controller receives persistentvolume\n                                claim update with ClaimResourceStatus for a resource\n                                that it does not recognizes, then it should ignore\n                                that update and let other controllers handle it.\n                              type: string\n                            description: \"allocatedResourceStatuses stores status\n                              of resource being resized for the given PVC. Key names\n                              follow standard Kubernetes label syntax. Valid values\n                              are either: * Un-prefixed keys: - storage - the capacity\n                              of the volume. * Custom resources must use implementation-defined\n                              prefixed names such as \\\"example.com/my-custom-resource\\\"\n                              Apart from above values - keys that are unprefixed or\n                              have kubernetes.io prefix are considered reserved and\n                              hence may not be used. \\n ClaimResourceStatus can be\n                              in any of following states: - ControllerResizeInProgress:\n                              State set when resize controller starts resizing the\n                              volume in control-plane. - ControllerResizeFailed: State\n                              set when resize has failed in resize controller with\n                              a terminal error. - NodeResizePending: State set when\n                              resize controller has finished resizing the volume but\n                              further resizing of volume is needed on the node. -\n                              NodeResizeInProgress: State set when kubelet starts\n                              resizing the volume. - NodeResizeFailed: State set when\n                              resizing has failed in kubelet with a terminal error.\n                              Transient errors don't set NodeResizeFailed. For example:\n                              if expanding a PVC for more capacity - this field can\n                              be one of the following states: - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"ControllerResizeInProgress\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"ControllerResizeFailed\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizePending\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizeInProgress\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizeFailed\\\" When this field is not set, it\n                              means that no resize operation is in progress for the\n                              given PVC. \\n A controller that receives PVC update\n                              with previously unknown resourceName or ClaimResourceStatus\n                              should ignore the update for the purpose it was designed.\n                              For example - a controller that only is responsible\n                              for resizing capacity of the volume, should ignore PVC\n                              updates that change other valid resources associated\n                              with PVC. \\n This is an alpha field and requires enabling\n                              RecoverVolumeExpansionFailure feature.\"\n                            type: object\n                            x-kubernetes-map-type: granular\n                          allocatedResources:\n                            additionalProperties:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                              x-kubernetes-int-or-string: true\n                            description: \"allocatedResources tracks the resources\n                              allocated to a PVC including its capacity. Key names\n                              follow standard Kubernetes label syntax. Valid values\n                              are either: * Un-prefixed keys: - storage - the capacity\n                              of the volume. * Custom resources must use implementation-defined\n                              prefixed names such as \\\"example.com/my-custom-resource\\\"\n                              Apart from above values - keys that are unprefixed or\n                              have kubernetes.io prefix are considered reserved and\n                              hence may not be used. \\n Capacity reported here may\n                              be larger than the actual capacity when a volume expansion\n                              operation is requested. For storage quota, the larger\n                              value from allocatedResources and PVC.spec.resources\n                              is used. If allocatedResources is not set, PVC.spec.resources\n                              alone is used for quota calculation. If a volume expansion\n                              capacity request is lowered, allocatedResources is only\n                              lowered if there are no expansion operations in progress\n                              and if the actual volume capacity is equal or lower\n                              than the requested capacity. \\n A controller that receives\n                              PVC update with previously unknown resourceName should\n                              ignore the update for the purpose it was designed. For\n                              example - a controller that only is responsible for\n                              resizing capacity of the volume, should ignore PVC updates\n                              that change other valid resources associated with PVC.\n                              \\n This is an alpha field and requires enabling RecoverVolumeExpansionFailure\n                              feature.\"\n                            type: object\n                          capacity:\n                            additionalProperties:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                              x-kubernetes-int-or-string: true\n                            description: capacity represents the actual resources\n                              of the underlying volume.\n                            type: object\n                          conditions:\n                            description: conditions is the current Condition of persistent\n                              volume claim. If underlying persistent volume is being\n                              resized then the Condition will be set to 'ResizeStarted'.\n                            items:\n                              description: PersistentVolumeClaimCondition contains\n                                details about state of pvc\n                              properties:\n                                lastProbeTime:\n                                  description: lastProbeTime is the time we probed\n                                    the condition.\n                                  format: date-time\n                                  type: string\n                                lastTransitionTime:\n                                  description: lastTransitionTime is the time the\n                                    condition transitioned from one status to another.\n                                  format: date-time\n                                  type: string\n                                message:\n                                  description: message is the human-readable message\n                                    indicating details about last transition.\n                                  type: string\n                                reason:\n                                  description: reason is a unique, this should be\n                                    a short, machine understandable string that gives\n                                    the reason for condition's last transition. If\n                                    it reports \"ResizeStarted\" that means the underlying\n                                    persistent volume is being resized.\n                                  type: string\n                                status:\n                                  type: string\n                                type:\n                                  description: PersistentVolumeClaimConditionType\n                                    is a valid value of PersistentVolumeClaimCondition.Type\n                                  type: string\n                              required:\n                              - status\n                              - type\n                              type: object\n                            type: array\n                          phase:\n                            description: phase represents the current phase of PersistentVolumeClaim.\n                            type: string\n                        type: object\n                    type: object\n                type: object\n              tag:\n                description: 'Tag of Alertmanager container image to be deployed.\n                  Defaults to the value of `version`. Version is ignored if Tag is\n                  set. Deprecated: use ''image'' instead. The image tag can be specified\n                  as part of the image URL.'\n                type: string\n              tolerations:\n                description: If specified, the pod's tolerations.\n                items:\n                  description: The pod this Toleration is attached to tolerates any\n                    taint that matches the triple <key,value,effect> using the matching\n                    operator <operator>.\n                  properties:\n                    effect:\n                      description: Effect indicates the taint effect to match. Empty\n                        means match all taint effects. When specified, allowed values\n                        are NoSchedule, PreferNoSchedule and NoExecute.\n                      type: string\n                    key:\n                      description: Key is the taint key that the toleration applies\n                        to. Empty means match all taint keys. If the key is empty,\n                        operator must be Exists; this combination means to match all\n                        values and all keys.\n                      type: string\n                    operator:\n                      description: Operator represents a key's relationship to the\n                        value. Valid operators are Exists and Equal. Defaults to Equal.\n                        Exists is equivalent to wildcard for value, so that a pod\n                        can tolerate all taints of a particular category.\n                      type: string\n                    tolerationSeconds:\n                      description: TolerationSeconds represents the period of time\n                        the toleration (which must be of effect NoExecute, otherwise\n                        this field is ignored) tolerates the taint. By default, it\n                        is not set, which means tolerate the taint forever (do not\n                        evict). Zero and negative values will be treated as 0 (evict\n                        immediately) by the system.\n                      format: int64\n                      type: integer\n                    value:\n                      description: Value is the taint value the toleration matches\n                        to. If the operator is Exists, the value should be empty,\n                        otherwise just a regular string.\n                      type: string\n                  type: object\n                type: array\n              topologySpreadConstraints:\n                description: If specified, the pod's topology spread constraints.\n                items:\n                  description: TopologySpreadConstraint specifies how to spread matching\n                    pods among the given topology.\n                  properties:\n                    labelSelector:\n                      description: LabelSelector is used to find matching pods. Pods\n                        that match this label selector are counted to determine the\n                        number of pods in their corresponding topology domain.\n                      properties:\n                        matchExpressions:\n                          description: matchExpressions is a list of label selector\n                            requirements. The requirements are ANDed.\n                          items:\n                            description: A label selector requirement is a selector\n                              that contains values, a key, and an operator that relates\n                              the key and values.\n                            properties:\n                              key:\n                                description: key is the label key that the selector\n                                  applies to.\n                                type: string\n                              operator:\n                                description: operator represents a key's relationship\n                                  to a set of values. Valid operators are In, NotIn,\n                                  Exists and DoesNotExist.\n                                type: string\n                              values:\n                                description: values is an array of string values.\n                                  If the operator is In or NotIn, the values array\n                                  must be non-empty. If the operator is Exists or\n                                  DoesNotExist, the values array must be empty. This\n                                  array is replaced during a strategic merge patch.\n                                items:\n                                  type: string\n                                type: array\n                            required:\n                            - key\n                            - operator\n                            type: object\n                          type: array\n                        matchLabels:\n                          additionalProperties:\n                            type: string\n                          description: matchLabels is a map of {key,value} pairs.\n                            A single {key,value} in the matchLabels map is equivalent\n                            to an element of matchExpressions, whose key field is\n                            \"key\", the operator is \"In\", and the values array contains\n                            only \"value\". The requirements are ANDed.\n                          type: object\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    matchLabelKeys:\n                      description: \"MatchLabelKeys is a set of pod label keys to select\n                        the pods over which spreading will be calculated. The keys\n                        are used to lookup values from the incoming pod labels, those\n                        key-value labels are ANDed with labelSelector to select the\n                        group of existing pods over which spreading will be calculated\n                        for the incoming pod. The same key is forbidden to exist in\n                        both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot\n                        be set when LabelSelector isn't set. Keys that don't exist\n                        in the incoming pod labels will be ignored. A null or empty\n                        list means only match against labelSelector. \\n This is a\n                        beta field and requires the MatchLabelKeysInPodTopologySpread\n                        feature gate to be enabled (enabled by default).\"\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    maxSkew:\n                      description: 'MaxSkew describes the degree to which pods may\n                        be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`,\n                        it is the maximum permitted difference between the number\n                        of matching pods in the target topology and the global minimum.\n                        The global minimum is the minimum number of matching pods\n                        in an eligible domain or zero if the number of eligible domains\n                        is less than MinDomains. For example, in a 3-zone cluster,\n                        MaxSkew is set to 1, and pods with the same labelSelector\n                        spread as 2/2/1: In this case, the global minimum is 1. |\n                        zone1 | zone2 | zone3 | |  P P  |  P P  |   P   | - if MaxSkew\n                        is 1, incoming pod can only be scheduled to zone3 to become\n                        2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1)\n                        on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming\n                        pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`,\n                        it is used to give higher precedence to topologies that satisfy\n                        it. It''s a required field. Default value is 1 and 0 is not\n                        allowed.'\n                      format: int32\n                      type: integer\n                    minDomains:\n                      description: \"MinDomains indicates a minimum number of eligible\n                        domains. When the number of eligible domains with matching\n                        topology keys is less than minDomains, Pod Topology Spread\n                        treats \\\"global minimum\\\" as 0, and then the calculation of\n                        Skew is performed. And when the number of eligible domains\n                        with matching topology keys equals or greater than minDomains,\n                        this value has no effect on scheduling. As a result, when\n                        the number of eligible domains is less than minDomains, scheduler\n                        won't schedule more than maxSkew Pods to those domains. If\n                        value is nil, the constraint behaves as if MinDomains is equal\n                        to 1. Valid values are integers greater than 0. When value\n                        is not nil, WhenUnsatisfiable must be DoNotSchedule. \\n For\n                        example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains\n                        is set to 5 and pods with the same labelSelector spread as\n                        2/2/2: | zone1 | zone2 | zone3 | |  P P  |  P P  |  P P  |\n                        The number of domains is less than 5(MinDomains), so \\\"global\n                        minimum\\\" is treated as 0. In this situation, new pod with\n                        the same labelSelector cannot be scheduled, because computed\n                        skew will be 3(3 - 0) if new Pod is scheduled to any of the\n                        three zones, it will violate MaxSkew. \\n This is a beta field\n                        and requires the MinDomainsInPodTopologySpread feature gate\n                        to be enabled (enabled by default).\"\n                      format: int32\n                      type: integer\n                    nodeAffinityPolicy:\n                      description: \"NodeAffinityPolicy indicates how we will treat\n                        Pod's nodeAffinity/nodeSelector when calculating pod topology\n                        spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector\n                        are included in the calculations. - Ignore: nodeAffinity/nodeSelector\n                        are ignored. All nodes are included in the calculations. \\n\n                        If this value is nil, the behavior is equivalent to the Honor\n                        policy. This is a beta-level feature default enabled by the\n                        NodeInclusionPolicyInPodTopologySpread feature flag.\"\n                      type: string\n                    nodeTaintsPolicy:\n                      description: \"NodeTaintsPolicy indicates how we will treat node\n                        taints when calculating pod topology spread skew. Options\n                        are: - Honor: nodes without taints, along with tainted nodes\n                        for which the incoming pod has a toleration, are included.\n                        - Ignore: node taints are ignored. All nodes are included.\n                        \\n If this value is nil, the behavior is equivalent to the\n                        Ignore policy. This is a beta-level feature default enabled\n                        by the NodeInclusionPolicyInPodTopologySpread feature flag.\"\n                      type: string\n                    topologyKey:\n                      description: TopologyKey is the key of node labels. Nodes that\n                        have a label with this key and identical values are considered\n                        to be in the same topology. We consider each <key, value>\n                        as a \"bucket\", and try to put balanced number of pods into\n                        each bucket. We define a domain as a particular instance of\n                        a topology. Also, we define an eligible domain as a domain\n                        whose nodes meet the requirements of nodeAffinityPolicy and\n                        nodeTaintsPolicy. e.g. If TopologyKey is \"kubernetes.io/hostname\",\n                        each Node is a domain of that topology. And, if TopologyKey\n                        is \"topology.kubernetes.io/zone\", each zone is a domain of\n                        that topology. It's a required field.\n                      type: string\n                    whenUnsatisfiable:\n                      description: 'WhenUnsatisfiable indicates how to deal with a\n                        pod if it doesn''t satisfy the spread constraint. - DoNotSchedule\n                        (default) tells the scheduler not to schedule it. - ScheduleAnyway\n                        tells the scheduler to schedule the pod in any location, but\n                        giving higher precedence to topologies that would help reduce\n                        the skew. A constraint is considered \"Unsatisfiable\" for an\n                        incoming pod if and only if every possible node assignment\n                        for that pod would violate \"MaxSkew\" on some topology. For\n                        example, in a 3-zone cluster, MaxSkew is set to 1, and pods\n                        with the same labelSelector spread as 3/1/1: | zone1 | zone2\n                        | zone3 | | P P P |   P   |   P   | If WhenUnsatisfiable is\n                        set to DoNotSchedule, incoming pod can only be scheduled to\n                        zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on\n                        zone2(zone3) satisfies MaxSkew(1). In other words, the cluster\n                        can still be imbalanced, but scheduler won''t make it *more*\n                        imbalanced. It''s a required field.'\n                      type: string\n                  required:\n                  - maxSkew\n                  - topologyKey\n                  - whenUnsatisfiable\n                  type: object\n                type: array\n              version:\n                description: Version the cluster should be on.\n                type: string\n              volumeMounts:\n                description: VolumeMounts allows configuration of additional VolumeMounts\n                  on the output StatefulSet definition. VolumeMounts specified will\n                  be appended to other VolumeMounts in the alertmanager container,\n                  that are generated as a result of StorageSpec objects.\n                items:\n                  description: VolumeMount describes a mounting of a Volume within\n                    a container.\n                  properties:\n                    mountPath:\n                      description: Path within the container at which the volume should\n                        be mounted.  Must not contain ':'.\n                      type: string\n                    mountPropagation:\n                      description: mountPropagation determines how mounts are propagated\n                        from the host to container and the other way around. When\n                        not set, MountPropagationNone is used. This field is beta\n                        in 1.10.\n                      type: string\n                    name:\n                      description: This must match the Name of a Volume.\n                      type: string\n                    readOnly:\n                      description: Mounted read-only if true, read-write otherwise\n                        (false or unspecified). Defaults to false.\n                      type: boolean\n                    subPath:\n                      description: Path within the volume from which the container's\n                        volume should be mounted. Defaults to \"\" (volume's root).\n                      type: string\n                    subPathExpr:\n                      description: Expanded path within the volume from which the\n                        container's volume should be mounted. Behaves similarly to\n                        SubPath but environment variable references $(VAR_NAME) are\n                        expanded using the container's environment. Defaults to \"\"\n                        (volume's root). SubPathExpr and SubPath are mutually exclusive.\n                      type: string\n                  required:\n                  - mountPath\n                  - name\n                  type: object\n                type: array\n              volumes:\n                description: Volumes allows configuration of additional volumes on\n                  the output StatefulSet definition. Volumes specified will be appended\n                  to other volumes that are generated as a result of StorageSpec objects.\n                items:\n                  description: Volume represents a named volume in a pod that may\n                    be accessed by any container in the pod.\n                  properties:\n                    awsElasticBlockStore:\n                      description: 'awsElasticBlockStore represents an AWS Disk resource\n                        that is attached to a kubelet''s host machine and then exposed\n                        to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        partition:\n                          description: 'partition is the partition in the volume that\n                            you want to mount. If omitted, the default is to mount\n                            by volume name. Examples: For volume /dev/sda1, you specify\n                            the partition as \"1\". Similarly, the volume partition\n                            for /dev/sda is \"0\" (or you can leave the property empty).'\n                          format: int32\n                          type: integer\n                        readOnly:\n                          description: 'readOnly value true will force the readOnly\n                            setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                          type: boolean\n                        volumeID:\n                          description: 'volumeID is unique ID of the persistent disk\n                            resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    azureDisk:\n                      description: azureDisk represents an Azure Data Disk mount on\n                        the host and bind mount to the pod.\n                      properties:\n                        cachingMode:\n                          description: 'cachingMode is the Host Caching mode: None,\n                            Read Only, Read Write.'\n                          type: string\n                        diskName:\n                          description: diskName is the Name of the data disk in the\n                            blob storage\n                          type: string\n                        diskURI:\n                          description: diskURI is the URI of data disk in the blob\n                            storage\n                          type: string\n                        fsType:\n                          description: fsType is Filesystem type to mount. Must be\n                            a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        kind:\n                          description: 'kind expected values are Shared: multiple\n                            blob disks per storage account  Dedicated: single blob\n                            disk per storage account  Managed: azure managed data\n                            disk (only in managed availability set). defaults to shared'\n                          type: string\n                        readOnly:\n                          description: readOnly Defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                      required:\n                      - diskName\n                      - diskURI\n                      type: object\n                    azureFile:\n                      description: azureFile represents an Azure File Service mount\n                        on the host and bind mount to the pod.\n                      properties:\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretName:\n                          description: secretName is the  name of secret that contains\n                            Azure Storage Account Name and Key\n                          type: string\n                        shareName:\n                          description: shareName is the azure share Name\n                          type: string\n                      required:\n                      - secretName\n                      - shareName\n                      type: object\n                    cephfs:\n                      description: cephFS represents a Ceph FS mount on the host that\n                        shares a pod's lifetime\n                      properties:\n                        monitors:\n                          description: 'monitors is Required: Monitors is a collection\n                            of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          items:\n                            type: string\n                          type: array\n                        path:\n                          description: 'path is Optional: Used as the mounted root,\n                            rather than the full Ceph tree, default is /'\n                          type: string\n                        readOnly:\n                          description: 'readOnly is Optional: Defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: boolean\n                        secretFile:\n                          description: 'secretFile is Optional: SecretFile is the\n                            path to key ring for User, default is /etc/ceph/user.secret\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: string\n                        secretRef:\n                          description: 'secretRef is Optional: SecretRef is reference\n                            to the authentication secret for User, default is empty.\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        user:\n                          description: 'user is optional: User is the rados user name,\n                            default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: string\n                      required:\n                      - monitors\n                      type: object\n                    cinder:\n                      description: 'cinder represents a cinder volume attached and\n                        mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to\n                            be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: string\n                        readOnly:\n                          description: 'readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                            More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is optional: points to a secret\n                            object containing parameters used to connect to OpenStack.'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        volumeID:\n                          description: 'volumeID used to identify the volume in cinder.\n                            More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    configMap:\n                      description: configMap represents a configMap that should populate\n                        this volume\n                      properties:\n                        defaultMode:\n                          description: 'defaultMode is optional: mode bits used to\n                            set permissions on created files by default. Must be an\n                            octal value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: items if unspecified, each key-value pair in\n                            the Data field of the referenced ConfigMap will be projected\n                            into the volume as a file whose name is the key and content\n                            is the value. If specified, the listed keys will be projected\n                            into the specified paths, and unlisted keys will not be\n                            present. If a key is specified which is not present in\n                            the ConfigMap, the volume setup will error unless it is\n                            marked optional. Paths must be relative and may not contain\n                            the '..' path or start with '..'.\n                          items:\n                            description: Maps a string key to a path within a volume.\n                            properties:\n                              key:\n                                description: key is the key to project.\n                                type: string\n                              mode:\n                                description: 'mode is Optional: mode bits used to\n                                  set permissions on this file. Must be an octal value\n                                  between 0000 and 0777 or a decimal value between\n                                  0 and 511. YAML accepts both octal and decimal values,\n                                  JSON requires decimal values for mode bits. If not\n                                  specified, the volume defaultMode will be used.\n                                  This might be in conflict with other options that\n                                  affect the file mode, like fsGroup, and the result\n                                  can be other mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: path is the relative path of the file\n                                  to map the key to. May not be an absolute path.\n                                  May not contain the path element '..'. May not start\n                                  with the string '..'.\n                                type: string\n                            required:\n                            - key\n                            - path\n                            type: object\n                          type: array\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: optional specify whether the ConfigMap or its\n                            keys must be defined\n                          type: boolean\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    csi:\n                      description: csi (Container Storage Interface) represents ephemeral\n                        storage that is handled by certain external CSI drivers (Beta\n                        feature).\n                      properties:\n                        driver:\n                          description: driver is the name of the CSI driver that handles\n                            this volume. Consult with your admin for the correct name\n                            as registered in the cluster.\n                          type: string\n                        fsType:\n                          description: fsType to mount. Ex. \"ext4\", \"xfs\", \"ntfs\".\n                            If not provided, the empty value is passed to the associated\n                            CSI driver which will determine the default filesystem\n                            to apply.\n                          type: string\n                        nodePublishSecretRef:\n                          description: nodePublishSecretRef is a reference to the\n                            secret object containing sensitive information to pass\n                            to the CSI driver to complete the CSI NodePublishVolume\n                            and NodeUnpublishVolume calls. This field is optional,\n                            and  may be empty if no secret is required. If the secret\n                            object contains more than one secret, all secret references\n                            are passed.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        readOnly:\n                          description: readOnly specifies a read-only configuration\n                            for the volume. Defaults to false (read/write).\n                          type: boolean\n                        volumeAttributes:\n                          additionalProperties:\n                            type: string\n                          description: volumeAttributes stores driver-specific properties\n                            that are passed to the CSI driver. Consult your driver's\n                            documentation for supported values.\n                          type: object\n                      required:\n                      - driver\n                      type: object\n                    downwardAPI:\n                      description: downwardAPI represents downward API about the pod\n                        that should populate this volume\n                      properties:\n                        defaultMode:\n                          description: 'Optional: mode bits to use on created files\n                            by default. Must be a Optional: mode bits used to set\n                            permissions on created files by default. Must be an octal\n                            value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: Items is a list of downward API volume file\n                          items:\n                            description: DownwardAPIVolumeFile represents information\n                              to create the file containing the pod field\n                            properties:\n                              fieldRef:\n                                description: 'Required: Selects a field of the pod:\n                                  only annotations, labels, name and namespace are\n                                  supported.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              mode:\n                                description: 'Optional: mode bits used to set permissions\n                                  on this file, must be an octal value between 0000\n                                  and 0777 or a decimal value between 0 and 511. YAML\n                                  accepts both octal and decimal values, JSON requires\n                                  decimal values for mode bits. If not specified,\n                                  the volume defaultMode will be used. This might\n                                  be in conflict with other options that affect the\n                                  file mode, like fsGroup, and the result can be other\n                                  mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: 'Required: Path is  the relative path\n                                  name of the file to be created. Must not be absolute\n                                  or contain the ''..'' path. Must be utf-8 encoded.\n                                  The first item of the relative path must not start\n                                  with ''..'''\n                                type: string\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, requests.cpu and requests.memory)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            required:\n                            - path\n                            type: object\n                          type: array\n                      type: object\n                    emptyDir:\n                      description: 'emptyDir represents a temporary directory that\n                        shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                      properties:\n                        medium:\n                          description: 'medium represents what type of storage medium\n                            should back this directory. The default is \"\" which means\n                            to use the node''s default medium. Must be an empty string\n                            (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                          type: string\n                        sizeLimit:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          description: 'sizeLimit is the total amount of local storage\n                            required for this EmptyDir volume. The size limit is also\n                            applicable for memory medium. The maximum usage on memory\n                            medium EmptyDir would be the minimum value between the\n                            SizeLimit specified here and the sum of memory limits\n                            of all containers in a pod. The default is nil which means\n                            that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                      type: object\n                    ephemeral:\n                      description: \"ephemeral represents a volume that is handled\n                        by a cluster storage driver. The volume's lifecycle is tied\n                        to the pod that defines it - it will be created before the\n                        pod starts, and deleted when the pod is removed. \\n Use this\n                        if: a) the volume is only needed while the pod runs, b) features\n                        of normal volumes like restoring from snapshot or capacity\n                        tracking are needed, c) the storage driver is specified through\n                        a storage class, and d) the storage driver supports dynamic\n                        volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource\n                        for more information on the connection between this volume\n                        type and PersistentVolumeClaim). \\n Use PersistentVolumeClaim\n                        or one of the vendor-specific APIs for volumes that persist\n                        for longer than the lifecycle of an individual pod. \\n Use\n                        CSI for light-weight local ephemeral volumes if the CSI driver\n                        is meant to be used that way - see the documentation of the\n                        driver for more information. \\n A pod can use both types of\n                        ephemeral volumes and persistent volumes at the same time.\"\n                      properties:\n                        volumeClaimTemplate:\n                          description: \"Will be used to create a stand-alone PVC to\n                            provision the volume. The pod in which this EphemeralVolumeSource\n                            is embedded will be the owner of the PVC, i.e. the PVC\n                            will be deleted together with the pod.  The name of the\n                            PVC will be `<pod name>-<volume name>` where `<volume\n                            name>` is the name from the `PodSpec.Volumes` array entry.\n                            Pod validation will reject the pod if the concatenated\n                            name is not valid for a PVC (for example, too long). \\n\n                            An existing PVC with that name that is not owned by the\n                            pod will *not* be used for the pod to avoid using an unrelated\n                            volume by mistake. Starting the pod is then blocked until\n                            the unrelated PVC is removed. If such a pre-created PVC\n                            is meant to be used by the pod, the PVC has to updated\n                            with an owner reference to the pod once the pod exists.\n                            Normally this should not be necessary, but it may be useful\n                            when manually reconstructing a broken cluster. \\n This\n                            field is read-only and no changes will be made by Kubernetes\n                            to the PVC after it has been created. \\n Required, must\n                            not be nil.\"\n                          properties:\n                            metadata:\n                              description: May contain labels and annotations that\n                                will be copied into the PVC when creating it. No other\n                                fields are allowed and will be rejected during validation.\n                              type: object\n                            spec:\n                              description: The specification for the PersistentVolumeClaim.\n                                The entire content is copied unchanged into the PVC\n                                that gets created from this template. The same fields\n                                as in a PersistentVolumeClaim are also valid here.\n                              properties:\n                                accessModes:\n                                  description: 'accessModes contains the desired access\n                                    modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                                  items:\n                                    type: string\n                                  type: array\n                                dataSource:\n                                  description: 'dataSource field can be used to specify\n                                    either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                                    * An existing PVC (PersistentVolumeClaim) If the\n                                    provisioner or an external controller can support\n                                    the specified data source, it will create a new\n                                    volume based on the contents of the specified\n                                    data source. When the AnyVolumeDataSource feature\n                                    gate is enabled, dataSource contents will be copied\n                                    to dataSourceRef, and dataSourceRef contents will\n                                    be copied to dataSource when dataSourceRef.namespace\n                                    is not specified. If the namespace is specified,\n                                    then dataSourceRef will not be copied to dataSource.'\n                                  properties:\n                                    apiGroup:\n                                      description: APIGroup is the group for the resource\n                                        being referenced. If APIGroup is not specified,\n                                        the specified Kind must be in the core API\n                                        group. For any other third-party types, APIGroup\n                                        is required.\n                                      type: string\n                                    kind:\n                                      description: Kind is the type of resource being\n                                        referenced\n                                      type: string\n                                    name:\n                                      description: Name is the name of resource being\n                                        referenced\n                                      type: string\n                                  required:\n                                  - kind\n                                  - name\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                dataSourceRef:\n                                  description: 'dataSourceRef specifies the object\n                                    from which to populate the volume with data, if\n                                    a non-empty volume is desired. This may be any\n                                    object from a non-empty API group (non core object)\n                                    or a PersistentVolumeClaim object. When this field\n                                    is specified, volume binding will only succeed\n                                    if the type of the specified object matches some\n                                    installed volume populator or dynamic provisioner.\n                                    This field will replace the functionality of the\n                                    dataSource field and as such if both fields are\n                                    non-empty, they must have the same value. For\n                                    backwards compatibility, when namespace isn''t\n                                    specified in dataSourceRef, both fields (dataSource\n                                    and dataSourceRef) will be set to the same value\n                                    automatically if one of them is empty and the\n                                    other is non-empty. When namespace is specified\n                                    in dataSourceRef, dataSource isn''t set to the\n                                    same value and must be empty. There are three\n                                    important differences between dataSource and dataSourceRef:\n                                    * While dataSource only allows two specific types\n                                    of objects, dataSourceRef allows any non-core\n                                    object, as well as PersistentVolumeClaim objects.\n                                    * While dataSource ignores disallowed values (dropping\n                                    them), dataSourceRef preserves all values, and\n                                    generates an error if a disallowed value is specified.\n                                    * While dataSource only allows local objects,\n                                    dataSourceRef allows objects in any namespaces.\n                                    (Beta) Using this field requires the AnyVolumeDataSource\n                                    feature gate to be enabled. (Alpha) Using the\n                                    namespace field of dataSourceRef requires the\n                                    CrossNamespaceVolumeDataSource feature gate to\n                                    be enabled.'\n                                  properties:\n                                    apiGroup:\n                                      description: APIGroup is the group for the resource\n                                        being referenced. If APIGroup is not specified,\n                                        the specified Kind must be in the core API\n                                        group. For any other third-party types, APIGroup\n                                        is required.\n                                      type: string\n                                    kind:\n                                      description: Kind is the type of resource being\n                                        referenced\n                                      type: string\n                                    name:\n                                      description: Name is the name of resource being\n                                        referenced\n                                      type: string\n                                    namespace:\n                                      description: Namespace is the namespace of resource\n                                        being referenced Note that when a namespace\n                                        is specified, a gateway.networking.k8s.io/ReferenceGrant\n                                        object is required in the referent namespace\n                                        to allow that namespace's owner to accept\n                                        the reference. See the ReferenceGrant documentation\n                                        for details. (Alpha) This field requires the\n                                        CrossNamespaceVolumeDataSource feature gate\n                                        to be enabled.\n                                      type: string\n                                  required:\n                                  - kind\n                                  - name\n                                  type: object\n                                resources:\n                                  description: 'resources represents the minimum resources\n                                    the volume should have. If RecoverVolumeExpansionFailure\n                                    feature is enabled users are allowed to specify\n                                    resource requirements that are lower than previous\n                                    value but must still be higher than capacity recorded\n                                    in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                                  properties:\n                                    claims:\n                                      description: \"Claims lists the names of resources,\n                                        defined in spec.resourceClaims, that are used\n                                        by this container. \\n This is an alpha field\n                                        and requires enabling the DynamicResourceAllocation\n                                        feature gate. \\n This field is immutable.\n                                        It can only be set for containers.\"\n                                      items:\n                                        description: ResourceClaim references one\n                                          entry in PodSpec.ResourceClaims.\n                                        properties:\n                                          name:\n                                            description: Name must match the name\n                                              of one entry in pod.spec.resourceClaims\n                                              of the Pod where this field is used.\n                                              It makes that resource available inside\n                                              a container.\n                                            type: string\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    limits:\n                                      additionalProperties:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                        x-kubernetes-int-or-string: true\n                                      description: 'Limits describes the maximum amount\n                                        of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                      type: object\n                                    requests:\n                                      additionalProperties:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                        x-kubernetes-int-or-string: true\n                                      description: 'Requests describes the minimum\n                                        amount of compute resources required. If Requests\n                                        is omitted for a container, it defaults to\n                                        Limits if that is explicitly specified, otherwise\n                                        to an implementation-defined value. Requests\n                                        cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                      type: object\n                                  type: object\n                                selector:\n                                  description: selector is a label query over volumes\n                                    to consider for binding.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                storageClassName:\n                                  description: 'storageClassName is the name of the\n                                    StorageClass required by the claim. More info:\n                                    https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                                  type: string\n                                volumeMode:\n                                  description: volumeMode defines what type of volume\n                                    is required by the claim. Value of Filesystem\n                                    is implied when not included in claim spec.\n                                  type: string\n                                volumeName:\n                                  description: volumeName is the binding reference\n                                    to the PersistentVolume backing this claim.\n                                  type: string\n                              type: object\n                          required:\n                          - spec\n                          type: object\n                      type: object\n                    fc:\n                      description: fc represents a Fibre Channel resource that is\n                        attached to a kubelet's host machine and then exposed to the\n                        pod.\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. TODO: how do we prevent errors in the\n                            filesystem from compromising the machine'\n                          type: string\n                        lun:\n                          description: 'lun is Optional: FC target lun number'\n                          format: int32\n                          type: integer\n                        readOnly:\n                          description: 'readOnly is Optional: Defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.'\n                          type: boolean\n                        targetWWNs:\n                          description: 'targetWWNs is Optional: FC target worldwide\n                            names (WWNs)'\n                          items:\n                            type: string\n                          type: array\n                        wwids:\n                          description: 'wwids Optional: FC volume world wide identifiers\n                            (wwids) Either wwids or combination of targetWWNs and\n                            lun must be set, but not both simultaneously.'\n                          items:\n                            type: string\n                          type: array\n                      type: object\n                    flexVolume:\n                      description: flexVolume represents a generic volume resource\n                        that is provisioned/attached using an exec based plugin.\n                      properties:\n                        driver:\n                          description: driver is the name of the driver to use for\n                            this volume.\n                          type: string\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends\n                            on FlexVolume script.\n                          type: string\n                        options:\n                          additionalProperties:\n                            type: string\n                          description: 'options is Optional: this field holds extra\n                            command options if any.'\n                          type: object\n                        readOnly:\n                          description: 'readOnly is Optional: defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is Optional: secretRef is reference\n                            to the secret object containing sensitive information\n                            to pass to the plugin scripts. This may be empty if no\n                            secret object is specified. If the secret object contains\n                            more than one secret, all secrets are passed to the plugin\n                            scripts.'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      required:\n                      - driver\n                      type: object\n                    flocker:\n                      description: flocker represents a Flocker volume attached to\n                        a kubelet's host machine. This depends on the Flocker control\n                        service being running\n                      properties:\n                        datasetName:\n                          description: datasetName is Name of the dataset stored as\n                            metadata -> name on the dataset for Flocker should be\n                            considered as deprecated\n                          type: string\n                        datasetUUID:\n                          description: datasetUUID is the UUID of the dataset. This\n                            is unique identifier of a Flocker dataset\n                          type: string\n                      type: object\n                    gcePersistentDisk:\n                      description: 'gcePersistentDisk represents a GCE Disk resource\n                        that is attached to a kubelet''s host machine and then exposed\n                        to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                      properties:\n                        fsType:\n                          description: 'fsType is filesystem type of the volume that\n                            you want to mount. Tip: Ensure that the filesystem type\n                            is supported by the host operating system. Examples: \"ext4\",\n                            \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        partition:\n                          description: 'partition is the partition in the volume that\n                            you want to mount. If omitted, the default is to mount\n                            by volume name. Examples: For volume /dev/sda1, you specify\n                            the partition as \"1\". Similarly, the volume partition\n                            for /dev/sda is \"0\" (or you can leave the property empty).\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          format: int32\n                          type: integer\n                        pdName:\n                          description: 'pdName is unique name of the PD resource in\n                            GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          type: boolean\n                      required:\n                      - pdName\n                      type: object\n                    gitRepo:\n                      description: 'gitRepo represents a git repository at a particular\n                        revision. DEPRECATED: GitRepo is deprecated. To provision\n                        a container with a git repo, mount an EmptyDir into an InitContainer\n                        that clones the repo using git, then mount the EmptyDir into\n                        the Pod''s container.'\n                      properties:\n                        directory:\n                          description: directory is the target directory name. Must\n                            not contain or start with '..'.  If '.' is supplied, the\n                            volume directory will be the git repository.  Otherwise,\n                            if specified, the volume will contain the git repository\n                            in the subdirectory with the given name.\n                          type: string\n                        repository:\n                          description: repository is the URL\n                          type: string\n                        revision:\n                          description: revision is the commit hash for the specified\n                            revision.\n                          type: string\n                      required:\n                      - repository\n                      type: object\n                    glusterfs:\n                      description: 'glusterfs represents a Glusterfs mount on the\n                        host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md'\n                      properties:\n                        endpoints:\n                          description: 'endpoints is the endpoint name that details\n                            Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: string\n                        path:\n                          description: 'path is the Glusterfs volume path. More info:\n                            https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the Glusterfs volume\n                            to be mounted with read-only permissions. Defaults to\n                            false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: boolean\n                      required:\n                      - endpoints\n                      - path\n                      type: object\n                    hostPath:\n                      description: 'hostPath represents a pre-existing file or directory\n                        on the host machine that is directly exposed to the container.\n                        This is generally used for system agents or other privileged\n                        things that are allowed to see the host machine. Most containers\n                        will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n                        --- TODO(jonesdl) We need to restrict who can use host directory\n                        mounts and who can/can not mount host directories as read/write.'\n                      properties:\n                        path:\n                          description: 'path of the directory on the host. If the\n                            path is a symlink, it will follow the link to the real\n                            path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                          type: string\n                        type:\n                          description: 'type for HostPath Volume Defaults to \"\" More\n                            info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                          type: string\n                      required:\n                      - path\n                      type: object\n                    iscsi:\n                      description: 'iscsi represents an ISCSI Disk resource that is\n                        attached to a kubelet''s host machine and then exposed to\n                        the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md'\n                      properties:\n                        chapAuthDiscovery:\n                          description: chapAuthDiscovery defines whether support iSCSI\n                            Discovery CHAP authentication\n                          type: boolean\n                        chapAuthSession:\n                          description: chapAuthSession defines whether support iSCSI\n                            Session CHAP authentication\n                          type: boolean\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        initiatorName:\n                          description: initiatorName is the custom iSCSI Initiator\n                            Name. If initiatorName is specified with iscsiInterface\n                            simultaneously, new iSCSI interface <target portal>:<volume\n                            name> will be created for the connection.\n                          type: string\n                        iqn:\n                          description: iqn is the target iSCSI Qualified Name.\n                          type: string\n                        iscsiInterface:\n                          description: iscsiInterface is the interface Name that uses\n                            an iSCSI transport. Defaults to 'default' (tcp).\n                          type: string\n                        lun:\n                          description: lun represents iSCSI Target Lun number.\n                          format: int32\n                          type: integer\n                        portals:\n                          description: portals is the iSCSI Target Portal List. The\n                            portal is either an IP or ip_addr:port if the port is\n                            other than default (typically TCP ports 860 and 3260).\n                          items:\n                            type: string\n                          type: array\n                        readOnly:\n                          description: readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false.\n                          type: boolean\n                        secretRef:\n                          description: secretRef is the CHAP Secret for iSCSI target\n                            and initiator authentication\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        targetPortal:\n                          description: targetPortal is iSCSI Target Portal. The Portal\n                            is either an IP or ip_addr:port if the port is other than\n                            default (typically TCP ports 860 and 3260).\n                          type: string\n                      required:\n                      - iqn\n                      - lun\n                      - targetPortal\n                      type: object\n                    name:\n                      description: 'name of the volume. Must be a DNS_LABEL and unique\n                        within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'\n                      type: string\n                    nfs:\n                      description: 'nfs represents an NFS mount on the host that shares\n                        a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                      properties:\n                        path:\n                          description: 'path that is exported by the NFS server. More\n                            info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the NFS export to\n                            be mounted with read-only permissions. Defaults to false.\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: boolean\n                        server:\n                          description: 'server is the hostname or IP address of the\n                            NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: string\n                      required:\n                      - path\n                      - server\n                      type: object\n                    persistentVolumeClaim:\n                      description: 'persistentVolumeClaimVolumeSource represents a\n                        reference to a PersistentVolumeClaim in the same namespace.\n                        More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                      properties:\n                        claimName:\n                          description: 'claimName is the name of a PersistentVolumeClaim\n                            in the same namespace as the pod using this volume. More\n                            info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                          type: string\n                        readOnly:\n                          description: readOnly Will force the ReadOnly setting in\n                            VolumeMounts. Default false.\n                          type: boolean\n                      required:\n                      - claimName\n                      type: object\n                    photonPersistentDisk:\n                      description: photonPersistentDisk represents a PhotonController\n                        persistent disk attached and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        pdID:\n                          description: pdID is the ID that identifies Photon Controller\n                            persistent disk\n                          type: string\n                      required:\n                      - pdID\n                      type: object\n                    portworxVolume:\n                      description: portworxVolume represents a portworx volume attached\n                        and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fSType represents the filesystem type to mount\n                            Must be a filesystem type supported by the host operating\n                            system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        volumeID:\n                          description: volumeID uniquely identifies a Portworx volume\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    projected:\n                      description: projected items for all in one resources secrets,\n                        configmaps, and downward API\n                      properties:\n                        defaultMode:\n                          description: defaultMode are the mode bits used to set permissions\n                            on created files by default. Must be an octal value between\n                            0000 and 0777 or a decimal value between 0 and 511. YAML\n                            accepts both octal and decimal values, JSON requires decimal\n                            values for mode bits. Directories within the path are\n                            not affected by this setting. This might be in conflict\n                            with other options that affect the file mode, like fsGroup,\n                            and the result can be other mode bits set.\n                          format: int32\n                          type: integer\n                        sources:\n                          description: sources is the list of volume projections\n                          items:\n                            description: Projection that may be projected along with\n                              other supported volume types\n                            properties:\n                              configMap:\n                                description: configMap information about the configMap\n                                  data to project\n                                properties:\n                                  items:\n                                    description: items if unspecified, each key-value\n                                      pair in the Data field of the referenced ConfigMap\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the ConfigMap, the volume\n                                      setup will error unless it is marked optional.\n                                      Paths must be relative and may not contain the\n                                      '..' path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: key is the key to project.\n                                          type: string\n                                        mode:\n                                          description: 'mode is Optional: mode bits\n                                            used to set permissions on this file.\n                                            Must be an octal value between 0000 and\n                                            0777 or a decimal value between 0 and\n                                            511. YAML accepts both octal and decimal\n                                            values, JSON requires decimal values for\n                                            mode bits. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: path is the relative path of\n                                            the file to map the key to. May not be\n                                            an absolute path. May not contain the\n                                            path element '..'. May not start with\n                                            the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: optional specify whether the ConfigMap\n                                      or its keys must be defined\n                                    type: boolean\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              downwardAPI:\n                                description: downwardAPI information about the downwardAPI\n                                  data to project\n                                properties:\n                                  items:\n                                    description: Items is a list of DownwardAPIVolume\n                                      file\n                                    items:\n                                      description: DownwardAPIVolumeFile represents\n                                        information to create the file containing\n                                        the pod field\n                                      properties:\n                                        fieldRef:\n                                          description: 'Required: Selects a field\n                                            of the pod: only annotations, labels,\n                                            name and namespace are supported.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        mode:\n                                          description: 'Optional: mode bits used to\n                                            set permissions on this file, must be\n                                            an octal value between 0000 and 0777 or\n                                            a decimal value between 0 and 511. YAML\n                                            accepts both octal and decimal values,\n                                            JSON requires decimal values for mode\n                                            bits. If not specified, the volume defaultMode\n                                            will be used. This might be in conflict\n                                            with other options that affect the file\n                                            mode, like fsGroup, and the result can\n                                            be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: 'Required: Path is  the relative\n                                            path name of the file to be created. Must\n                                            not be absolute or contain the ''..''\n                                            path. Must be utf-8 encoded. The first\n                                            item of the relative path must not start\n                                            with ''..'''\n                                          type: string\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, requests.cpu\n                                            and requests.memory) are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - path\n                                      type: object\n                                    type: array\n                                type: object\n                              secret:\n                                description: secret information about the secret data\n                                  to project\n                                properties:\n                                  items:\n                                    description: items if unspecified, each key-value\n                                      pair in the Data field of the referenced Secret\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the Secret, the volume setup\n                                      will error unless it is marked optional. Paths\n                                      must be relative and may not contain the '..'\n                                      path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: key is the key to project.\n                                          type: string\n                                        mode:\n                                          description: 'mode is Optional: mode bits\n                                            used to set permissions on this file.\n                                            Must be an octal value between 0000 and\n                                            0777 or a decimal value between 0 and\n                                            511. YAML accepts both octal and decimal\n                                            values, JSON requires decimal values for\n                                            mode bits. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: path is the relative path of\n                                            the file to map the key to. May not be\n                                            an absolute path. May not contain the\n                                            path element '..'. May not start with\n                                            the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: optional field specify whether the\n                                      Secret or its key must be defined\n                                    type: boolean\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              serviceAccountToken:\n                                description: serviceAccountToken is information about\n                                  the serviceAccountToken data to project\n                                properties:\n                                  audience:\n                                    description: audience is the intended audience\n                                      of the token. A recipient of a token must identify\n                                      itself with an identifier specified in the audience\n                                      of the token, and otherwise should reject the\n                                      token. The audience defaults to the identifier\n                                      of the apiserver.\n                                    type: string\n                                  expirationSeconds:\n                                    description: expirationSeconds is the requested\n                                      duration of validity of the service account\n                                      token. As the token approaches expiration, the\n                                      kubelet volume plugin will proactively rotate\n                                      the service account token. The kubelet will\n                                      start trying to rotate the token if the token\n                                      is older than 80 percent of its time to live\n                                      or if the token is older than 24 hours.Defaults\n                                      to 1 hour and must be at least 10 minutes.\n                                    format: int64\n                                    type: integer\n                                  path:\n                                    description: path is the path relative to the\n                                      mount point of the file to project the token\n                                      into.\n                                    type: string\n                                required:\n                                - path\n                                type: object\n                            type: object\n                          type: array\n                      type: object\n                    quobyte:\n                      description: quobyte represents a Quobyte mount on the host\n                        that shares a pod's lifetime\n                      properties:\n                        group:\n                          description: group to map volume access to Default is no\n                            group\n                          type: string\n                        readOnly:\n                          description: readOnly here will force the Quobyte volume\n                            to be mounted with read-only permissions. Defaults to\n                            false.\n                          type: boolean\n                        registry:\n                          description: registry represents a single or multiple Quobyte\n                            Registry services specified as a string as host:port pair\n                            (multiple entries are separated with commas) which acts\n                            as the central registry for volumes\n                          type: string\n                        tenant:\n                          description: tenant owning the given Quobyte volume in the\n                            Backend Used with dynamically provisioned Quobyte volumes,\n                            value is set by the plugin\n                          type: string\n                        user:\n                          description: user to map volume access to Defaults to serivceaccount\n                            user\n                          type: string\n                        volume:\n                          description: volume is a string that references an already\n                            created Quobyte volume by name.\n                          type: string\n                      required:\n                      - registry\n                      - volume\n                      type: object\n                    rbd:\n                      description: 'rbd represents a Rados Block Device mount on the\n                        host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        image:\n                          description: 'image is the rados image name. More info:\n                            https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        keyring:\n                          description: 'keyring is the path to key ring for RBDUser.\n                            Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        monitors:\n                          description: 'monitors is a collection of Ceph monitors.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          items:\n                            type: string\n                          type: array\n                        pool:\n                          description: 'pool is the rados pool name. Default is rbd.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is name of the authentication secret\n                            for RBDUser. If provided overrides keyring. Default is\n                            nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        user:\n                          description: 'user is the rados user name. Default is admin.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                      required:\n                      - image\n                      - monitors\n                      type: object\n                    scaleIO:\n                      description: scaleIO represents a ScaleIO persistent volume\n                        attached and mounted on Kubernetes nodes.\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\".\n                          type: string\n                        gateway:\n                          description: gateway is the host address of the ScaleIO\n                            API Gateway.\n                          type: string\n                        protectionDomain:\n                          description: protectionDomain is the name of the ScaleIO\n                            Protection Domain for the configured storage.\n                          type: string\n                        readOnly:\n                          description: readOnly Defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretRef:\n                          description: secretRef references to the secret for ScaleIO\n                            user and other sensitive information. If this is not provided,\n                            Login operation will fail.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        sslEnabled:\n                          description: sslEnabled Flag enable/disable SSL communication\n                            with Gateway, default false\n                          type: boolean\n                        storageMode:\n                          description: storageMode indicates whether the storage for\n                            a volume should be ThickProvisioned or ThinProvisioned.\n                            Default is ThinProvisioned.\n                          type: string\n                        storagePool:\n                          description: storagePool is the ScaleIO Storage Pool associated\n                            with the protection domain.\n                          type: string\n                        system:\n                          description: system is the name of the storage system as\n                            configured in ScaleIO.\n                          type: string\n                        volumeName:\n                          description: volumeName is the name of a volume already\n                            created in the ScaleIO system that is associated with\n                            this volume source.\n                          type: string\n                      required:\n                      - gateway\n                      - secretRef\n                      - system\n                      type: object\n                    secret:\n                      description: 'secret represents a secret that should populate\n                        this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                      properties:\n                        defaultMode:\n                          description: 'defaultMode is Optional: mode bits used to\n                            set permissions on created files by default. Must be an\n                            octal value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: items If unspecified, each key-value pair in\n                            the Data field of the referenced Secret will be projected\n                            into the volume as a file whose name is the key and content\n                            is the value. If specified, the listed keys will be projected\n                            into the specified paths, and unlisted keys will not be\n                            present. If a key is specified which is not present in\n                            the Secret, the volume setup will error unless it is marked\n                            optional. Paths must be relative and may not contain the\n                            '..' path or start with '..'.\n                          items:\n                            description: Maps a string key to a path within a volume.\n                            properties:\n                              key:\n                                description: key is the key to project.\n                                type: string\n                              mode:\n                                description: 'mode is Optional: mode bits used to\n                                  set permissions on this file. Must be an octal value\n                                  between 0000 and 0777 or a decimal value between\n                                  0 and 511. YAML accepts both octal and decimal values,\n                                  JSON requires decimal values for mode bits. If not\n                                  specified, the volume defaultMode will be used.\n                                  This might be in conflict with other options that\n                                  affect the file mode, like fsGroup, and the result\n                                  can be other mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: path is the relative path of the file\n                                  to map the key to. May not be an absolute path.\n                                  May not contain the path element '..'. May not start\n                                  with the string '..'.\n                                type: string\n                            required:\n                            - key\n                            - path\n                            type: object\n                          type: array\n                        optional:\n                          description: optional field specify whether the Secret or\n                            its keys must be defined\n                          type: boolean\n                        secretName:\n                          description: 'secretName is the name of the secret in the\n                            pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                          type: string\n                      type: object\n                    storageos:\n                      description: storageOS represents a StorageOS volume attached\n                        and mounted on Kubernetes nodes.\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretRef:\n                          description: secretRef specifies the secret to use for obtaining\n                            the StorageOS API credentials.  If not specified, default\n                            values will be attempted.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        volumeName:\n                          description: volumeName is the human-readable name of the\n                            StorageOS volume.  Volume names are only unique within\n                            a namespace.\n                          type: string\n                        volumeNamespace:\n                          description: volumeNamespace specifies the scope of the\n                            volume within StorageOS.  If no namespace is specified\n                            then the Pod's namespace will be used.  This allows the\n                            Kubernetes name scoping to be mirrored within StorageOS\n                            for tighter integration. Set VolumeName to any name to\n                            override the default behaviour. Set to \"default\" if you\n                            are not using namespaces within StorageOS. Namespaces\n                            that do not pre-exist within StorageOS will be created.\n                          type: string\n                      type: object\n                    vsphereVolume:\n                      description: vsphereVolume represents a vSphere volume attached\n                        and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fsType is filesystem type to mount. Must be\n                            a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        storagePolicyID:\n                          description: storagePolicyID is the storage Policy Based\n                            Management (SPBM) profile ID associated with the StoragePolicyName.\n                          type: string\n                        storagePolicyName:\n                          description: storagePolicyName is the storage Policy Based\n                            Management (SPBM) profile name.\n                          type: string\n                        volumePath:\n                          description: volumePath is the path that identifies vSphere\n                            volume vmdk\n                          type: string\n                      required:\n                      - volumePath\n                      type: object\n                  required:\n                  - name\n                  type: object\n                type: array\n              web:\n                description: Defines the web command line flags when starting Alertmanager.\n                properties:\n                  getConcurrency:\n                    description: Maximum number of GET requests processed concurrently.\n                      This corresponds to the Alertmanager's `--web.get-concurrency`\n                      flag.\n                    format: int32\n                    type: integer\n                  httpConfig:\n                    description: Defines HTTP parameters for web server.\n                    properties:\n                      headers:\n                        description: List of headers that can be added to HTTP responses.\n                        properties:\n                          contentSecurityPolicy:\n                            description: Set the Content-Security-Policy header to\n                              HTTP responses. Unset if blank.\n                            type: string\n                          strictTransportSecurity:\n                            description: Set the Strict-Transport-Security header\n                              to HTTP responses. Unset if blank. Please make sure\n                              that you use this with care as this header might force\n                              browsers to load Prometheus and the other applications\n                              hosted on the same domain and subdomains over HTTPS.\n                              https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security\n                            type: string\n                          xContentTypeOptions:\n                            description: Set the X-Content-Type-Options header to\n                              HTTP responses. Unset if blank. Accepted value is nosniff.\n                              https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options\n                            enum:\n                            - \"\"\n                            - NoSniff\n                            type: string\n                          xFrameOptions:\n                            description: Set the X-Frame-Options header to HTTP responses.\n                              Unset if blank. Accepted values are deny and sameorigin.\n                              https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options\n                            enum:\n                            - \"\"\n                            - Deny\n                            - SameOrigin\n                            type: string\n                          xXSSProtection:\n                            description: Set the X-XSS-Protection header to all responses.\n                              Unset if blank. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection\n                            type: string\n                        type: object\n                      http2:\n                        description: Enable HTTP/2 support. Note that HTTP/2 is only\n                          supported with TLS. When TLSConfig is not configured, HTTP/2\n                          will be disabled. Whenever the value of the field changes,\n                          a rolling update will be triggered.\n                        type: boolean\n                    type: object\n                  timeout:\n                    description: Timeout for HTTP requests. This corresponds to the\n                      Alertmanager's `--web.timeout` flag.\n                    format: int32\n                    type: integer\n                  tlsConfig:\n                    description: Defines the TLS parameters for HTTPS.\n                    properties:\n                      cert:\n                        description: Contains the TLS certificate for the server.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      cipherSuites:\n                        description: 'List of supported cipher suites for TLS versions\n                          up to TLS 1.2. If empty, Go default cipher suites are used.\n                          Available cipher suites are documented in the go documentation:\n                          https://golang.org/pkg/crypto/tls/#pkg-constants'\n                        items:\n                          type: string\n                        type: array\n                      client_ca:\n                        description: Contains the CA certificate for client certificate\n                          authentication to the server.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      clientAuthType:\n                        description: 'Server policy for client authentication. Maps\n                          to ClientAuth Policies. For more detail on clientAuth options:\n                          https://golang.org/pkg/crypto/tls/#ClientAuthType'\n                        type: string\n                      curvePreferences:\n                        description: 'Elliptic curves that will be used in an ECDHE\n                          handshake, in preference order. Available curves are documented\n                          in the go documentation: https://golang.org/pkg/crypto/tls/#CurveID'\n                        items:\n                          type: string\n                        type: array\n                      keySecret:\n                        description: Secret containing the TLS key for the server.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      maxVersion:\n                        description: Maximum TLS version that is acceptable. Defaults\n                          to TLS13.\n                        type: string\n                      minVersion:\n                        description: Minimum TLS version that is acceptable. Defaults\n                          to TLS12.\n                        type: string\n                      preferServerCipherSuites:\n                        description: Controls whether the server selects the client's\n                          most preferred cipher suite, or the server's most preferred\n                          cipher suite. If true then the server's preference, as expressed\n                          in the order of elements in cipherSuites, is used.\n                        type: boolean\n                    required:\n                    - cert\n                    - keySecret\n                    type: object\n                type: object\n            type: object\n          status:\n            description: 'Most recent observed status of the Alertmanager cluster.\n              Read-only. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n            properties:\n              availableReplicas:\n                description: Total number of available pods (ready for at least minReadySeconds)\n                  targeted by this Alertmanager cluster.\n                format: int32\n                type: integer\n              conditions:\n                description: The current state of the Alertmanager object.\n                items:\n                  description: Condition represents the state of the resources associated\n                    with the Prometheus, Alertmanager or ThanosRuler resource.\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the time of the last update\n                        to the current status property.\n                      format: date-time\n                      type: string\n                    message:\n                      description: Human-readable message indicating details for the\n                        condition's last transition.\n                      type: string\n                    observedGeneration:\n                      description: ObservedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if `.metadata.generation`\n                        is currently 12, but the `.status.conditions[].observedGeneration`\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      type: integer\n                    reason:\n                      description: Reason for the condition's last transition.\n                      type: string\n                    status:\n                      description: Status of the condition.\n                      type: string\n                    type:\n                      description: Type of the condition being reported.\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              paused:\n                description: Represents whether any actions on the underlying managed\n                  objects are being performed. Only delete actions will be performed.\n                type: boolean\n              replicas:\n                description: Total number of non-terminated pods targeted by this\n                  Alertmanager object (their labels match the selector).\n                format: int32\n                type: integer\n              unavailableReplicas:\n                description: Total number of unavailable pods targeted by this Alertmanager\n                  object.\n                format: int32\n                type: integer\n              updatedReplicas:\n                description: Total number of non-terminated pods targeted by this\n                  Alertmanager object that have the desired version spec.\n                format: int32\n                type: integer\n            required:\n            - availableReplicas\n            - paused\n            - replicas\n            - unavailableReplicas\n            - updatedReplicas\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: prometheuses.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: Prometheus\n    listKind: PrometheusList\n    plural: prometheuses\n    shortNames:\n    - prom\n    singular: prometheus\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - description: The version of Prometheus\n      jsonPath: .spec.version\n      name: Version\n      type: string\n    - description: The number of desired replicas\n      jsonPath: .spec.replicas\n      name: Desired\n      type: integer\n    - description: The number of ready replicas\n      jsonPath: .status.availableReplicas\n      name: Ready\n      type: integer\n    - jsonPath: .status.conditions[?(@.type == 'Reconciled')].status\n      name: Reconciled\n      type: string\n    - jsonPath: .status.conditions[?(@.type == 'Available')].status\n      name: Available\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - description: Whether the resource reconciliation is paused or not\n      jsonPath: .status.paused\n      name: Paused\n      priority: 1\n      type: boolean\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: Prometheus defines a Prometheus deployment.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: 'Specification of the desired behavior of the Prometheus\n              cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n            properties:\n              additionalAlertManagerConfigs:\n                description: \"AdditionalAlertManagerConfigs specifies a key of a Secret\n                  containing additional Prometheus Alertmanager configurations. The\n                  Alertmanager configurations are appended to the configuration generated\n                  by the Prometheus Operator. They must be formatted according to\n                  the official Prometheus documentation: \\n https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config\n                  \\n The user is responsible for making sure that the configurations\n                  are valid \\n Note that using this feature may expose the possibility\n                  to break upgrades of Prometheus. It is advised to review Prometheus\n                  release notes to ensure that no incompatible AlertManager configs\n                  are going to break Prometheus after the upgrade.\"\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              additionalAlertRelabelConfigs:\n                description: \"AdditionalAlertRelabelConfigs specifies a key of a Secret\n                  containing additional Prometheus alert relabel configurations. The\n                  alert relabel configurations are appended to the configuration generated\n                  by the Prometheus Operator. They must be formatted according to\n                  the official Prometheus documentation: \\n https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alert_relabel_configs\n                  \\n The user is responsible for making sure that the configurations\n                  are valid \\n Note that using this feature may expose the possibility\n                  to break upgrades of Prometheus. It is advised to review Prometheus\n                  release notes to ensure that no incompatible alert relabel configs\n                  are going to break Prometheus after the upgrade.\"\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              additionalArgs:\n                description: \"AdditionalArgs allows setting additional arguments for\n                  the 'prometheus' container. \\n It is intended for e.g. activating\n                  hidden flags which are not supported by the dedicated configuration\n                  options yet. The arguments are passed as-is to the Prometheus container\n                  which may cause issues if they are invalid or not supported by the\n                  given Prometheus version. \\n In case of an argument conflict (e.g.\n                  an argument which is already set by the operator itself) or when\n                  providing an invalid argument, the reconciliation will fail and\n                  an error will be logged.\"\n                items:\n                  description: Argument as part of the AdditionalArgs list.\n                  properties:\n                    name:\n                      description: Name of the argument, e.g. \"scrape.discovery-reload-interval\".\n                      minLength: 1\n                      type: string\n                    value:\n                      description: Argument value, e.g. 30s. Can be empty for name-only\n                        arguments (e.g. --storage.tsdb.no-lockfile)\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              additionalScrapeConfigs:\n                description: 'AdditionalScrapeConfigs allows specifying a key of a\n                  Secret containing additional Prometheus scrape configurations. Scrape\n                  configurations specified are appended to the configurations generated\n                  by the Prometheus Operator. Job configurations specified must have\n                  the form as specified in the official Prometheus documentation:\n                  https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config.\n                  As scrape configs are appended, the user is responsible to make\n                  sure it is valid. Note that using this feature may expose the possibility\n                  to break upgrades of Prometheus. It is advised to review Prometheus\n                  release notes to ensure that no incompatible scrape configs are\n                  going to break Prometheus after the upgrade.'\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              affinity:\n                description: Defines the Pods' affinity scheduling rules if specified.\n                properties:\n                  nodeAffinity:\n                    description: Describes node affinity scheduling rules for the\n                      pod.\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the affinity expressions specified by\n                          this field, but it may choose a node that violates one or\n                          more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node matches\n                          the corresponding matchExpressions; the node(s) with the\n                          highest sum are the most preferred.\n                        items:\n                          description: An empty preferred scheduling term matches\n                            all objects with implicit weight 0 (i.e. it's a no-op).\n                            A null preferred scheduling term matches no objects (i.e.\n                            is also a no-op).\n                          properties:\n                            preference:\n                              description: A node selector term, associated with the\n                                corresponding weight.\n                              properties:\n                                matchExpressions:\n                                  description: A list of node selector requirements\n                                    by node's labels.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchFields:\n                                  description: A list of node selector requirements\n                                    by node's fields.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            weight:\n                              description: Weight associated with matching the corresponding\n                                nodeSelectorTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - preference\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the affinity requirements specified by this\n                          field are not met at scheduling time, the pod will not be\n                          scheduled onto the node. If the affinity requirements specified\n                          by this field cease to be met at some point during pod execution\n                          (e.g. due to an update), the system may or may not try to\n                          eventually evict the pod from its node.\n                        properties:\n                          nodeSelectorTerms:\n                            description: Required. A list of node selector terms.\n                              The terms are ORed.\n                            items:\n                              description: A null or empty node selector term matches\n                                no objects. The requirements of them are ANDed. The\n                                TopologySelectorTerm type implements a subset of the\n                                NodeSelectorTerm.\n                              properties:\n                                matchExpressions:\n                                  description: A list of node selector requirements\n                                    by node's labels.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchFields:\n                                  description: A list of node selector requirements\n                                    by node's fields.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            type: array\n                        required:\n                        - nodeSelectorTerms\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  podAffinity:\n                    description: Describes pod affinity scheduling rules (e.g. co-locate\n                      this pod in the same node, zone, etc. as some other pod(s)).\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the affinity expressions specified by\n                          this field, but it may choose a node that violates one or\n                          more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node has\n                          pods which matches the corresponding podAffinityTerm; the\n                          node(s) with the highest sum are the most preferred.\n                        items:\n                          description: The weights of all of the matched WeightedPodAffinityTerm\n                            fields are added per-node to find the most preferred node(s)\n                          properties:\n                            podAffinityTerm:\n                              description: Required. A pod affinity term, associated\n                                with the corresponding weight.\n                              properties:\n                                labelSelector:\n                                  description: A label query over a set of resources,\n                                    in this case pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaceSelector:\n                                  description: A label query over the set of namespaces\n                                    that the term applies to. The term is applied\n                                    to the union of the namespaces selected by this\n                                    field and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list\n                                    means \"this pod's namespace\". An empty selector\n                                    ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: namespaces specifies a static list\n                                    of namespace names that the term applies to. The\n                                    term is applied to the union of the namespaces\n                                    listed in this field and the ones selected by\n                                    namespaceSelector. null or empty namespaces list\n                                    and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                topologyKey:\n                                  description: This pod should be co-located (affinity)\n                                    or not co-located (anti-affinity) with the pods\n                                    matching the labelSelector in the specified namespaces,\n                                    where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey\n                                    matches that of any node on which any of the selected\n                                    pods is running. Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            weight:\n                              description: weight associated with matching the corresponding\n                                podAffinityTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - podAffinityTerm\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the affinity requirements specified by this\n                          field are not met at scheduling time, the pod will not be\n                          scheduled onto the node. If the affinity requirements specified\n                          by this field cease to be met at some point during pod execution\n                          (e.g. due to a pod label update), the system may or may\n                          not try to eventually evict the pod from its node. When\n                          there are multiple elements, the lists of nodes corresponding\n                          to each podAffinityTerm are intersected, i.e. all terms\n                          must be satisfied.\n                        items:\n                          description: Defines a set of pods (namely those matching\n                            the labelSelector relative to the given namespace(s))\n                            that this pod should be co-located (affinity) or not co-located\n                            (anti-affinity) with, where co-located is defined as running\n                            on a node whose value of the label with key <topologyKey>\n                            matches that of any node on which a pod of the set of\n                            pods is running\n                          properties:\n                            labelSelector:\n                              description: A label query over a set of resources,\n                                in this case pods.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaceSelector:\n                              description: A label query over the set of namespaces\n                                that the term applies to. The term is applied to the\n                                union of the namespaces selected by this field and\n                                the ones listed in the namespaces field. null selector\n                                and null or empty namespaces list means \"this pod's\n                                namespace\". An empty selector ({}) matches all namespaces.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaces:\n                              description: namespaces specifies a static list of namespace\n                                names that the term applies to. The term is applied\n                                to the union of the namespaces listed in this field\n                                and the ones selected by namespaceSelector. null or\n                                empty namespaces list and null namespaceSelector means\n                                \"this pod's namespace\".\n                              items:\n                                type: string\n                              type: array\n                            topologyKey:\n                              description: This pod should be co-located (affinity)\n                                or not co-located (anti-affinity) with the pods matching\n                                the labelSelector in the specified namespaces, where\n                                co-located is defined as running on a node whose value\n                                of the label with key topologyKey matches that of\n                                any node on which any of the selected pods is running.\n                                Empty topologyKey is not allowed.\n                              type: string\n                          required:\n                          - topologyKey\n                          type: object\n                        type: array\n                    type: object\n                  podAntiAffinity:\n                    description: Describes pod anti-affinity scheduling rules (e.g.\n                      avoid putting this pod in the same node, zone, etc. as some\n                      other pod(s)).\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the anti-affinity expressions specified\n                          by this field, but it may choose a node that violates one\n                          or more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling anti-affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node has\n                          pods which matches the corresponding podAffinityTerm; the\n                          node(s) with the highest sum are the most preferred.\n                        items:\n                          description: The weights of all of the matched WeightedPodAffinityTerm\n                            fields are added per-node to find the most preferred node(s)\n                          properties:\n                            podAffinityTerm:\n                              description: Required. A pod affinity term, associated\n                                with the corresponding weight.\n                              properties:\n                                labelSelector:\n                                  description: A label query over a set of resources,\n                                    in this case pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaceSelector:\n                                  description: A label query over the set of namespaces\n                                    that the term applies to. The term is applied\n                                    to the union of the namespaces selected by this\n                                    field and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list\n                                    means \"this pod's namespace\". An empty selector\n                                    ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: namespaces specifies a static list\n                                    of namespace names that the term applies to. The\n                                    term is applied to the union of the namespaces\n                                    listed in this field and the ones selected by\n                                    namespaceSelector. null or empty namespaces list\n                                    and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                topologyKey:\n                                  description: This pod should be co-located (affinity)\n                                    or not co-located (anti-affinity) with the pods\n                                    matching the labelSelector in the specified namespaces,\n                                    where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey\n                                    matches that of any node on which any of the selected\n                                    pods is running. Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            weight:\n                              description: weight associated with matching the corresponding\n                                podAffinityTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - podAffinityTerm\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the anti-affinity requirements specified by\n                          this field are not met at scheduling time, the pod will\n                          not be scheduled onto the node. If the anti-affinity requirements\n                          specified by this field cease to be met at some point during\n                          pod execution (e.g. due to a pod label update), the system\n                          may or may not try to eventually evict the pod from its\n                          node. When there are multiple elements, the lists of nodes\n                          corresponding to each podAffinityTerm are intersected, i.e.\n                          all terms must be satisfied.\n                        items:\n                          description: Defines a set of pods (namely those matching\n                            the labelSelector relative to the given namespace(s))\n                            that this pod should be co-located (affinity) or not co-located\n                            (anti-affinity) with, where co-located is defined as running\n                            on a node whose value of the label with key <topologyKey>\n                            matches that of any node on which a pod of the set of\n                            pods is running\n                          properties:\n                            labelSelector:\n                              description: A label query over a set of resources,\n                                in this case pods.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaceSelector:\n                              description: A label query over the set of namespaces\n                                that the term applies to. The term is applied to the\n                                union of the namespaces selected by this field and\n                                the ones listed in the namespaces field. null selector\n                                and null or empty namespaces list means \"this pod's\n                                namespace\". An empty selector ({}) matches all namespaces.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaces:\n                              description: namespaces specifies a static list of namespace\n                                names that the term applies to. The term is applied\n                                to the union of the namespaces listed in this field\n                                and the ones selected by namespaceSelector. null or\n                                empty namespaces list and null namespaceSelector means\n                                \"this pod's namespace\".\n                              items:\n                                type: string\n                              type: array\n                            topologyKey:\n                              description: This pod should be co-located (affinity)\n                                or not co-located (anti-affinity) with the pods matching\n                                the labelSelector in the specified namespaces, where\n                                co-located is defined as running on a node whose value\n                                of the label with key topologyKey matches that of\n                                any node on which any of the selected pods is running.\n                                Empty topologyKey is not allowed.\n                              type: string\n                          required:\n                          - topologyKey\n                          type: object\n                        type: array\n                    type: object\n                type: object\n              alerting:\n                description: Defines the settings related to Alertmanager.\n                properties:\n                  alertmanagers:\n                    description: AlertmanagerEndpoints Prometheus should fire alerts\n                      against.\n                    items:\n                      description: AlertmanagerEndpoints defines a selection of a\n                        single Endpoints object containing Alertmanager IPs to fire\n                        alerts against.\n                      properties:\n                        apiVersion:\n                          description: Version of the Alertmanager API that Prometheus\n                            uses to send alerts. It can be \"v1\" or \"v2\".\n                          type: string\n                        authorization:\n                          description: \"Authorization section for Alertmanager. \\n\n                            Cannot be set at the same time as `basicAuth`, `bearerTokenFile`\n                            or `sigv4`.\"\n                          properties:\n                            credentials:\n                              description: Selects a key of a Secret in the namespace\n                                that contains the credentials for authentication.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            type:\n                              description: \"Defines the authentication type. The value\n                                is case-insensitive. \\n \\\"Basic\\\" is not a supported\n                                value. \\n Default: \\\"Bearer\\\"\"\n                              type: string\n                          type: object\n                        basicAuth:\n                          description: \"BasicAuth configuration for Alertmanager.\n                            \\n Cannot be set at the same time as `bearerTokenFile`,\n                            `authorization` or `sigv4`.\"\n                          properties:\n                            password:\n                              description: '`password` specifies a key of a Secret\n                                containing the password for authentication.'\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            username:\n                              description: '`username` specifies a key of a Secret\n                                containing the username for authentication.'\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        bearerTokenFile:\n                          description: \"File to read bearer token for Alertmanager.\n                            \\n Cannot be set at the same time as `basicAuth`, `authorization`,\n                            or `sigv4`. \\n Deprecated: this will be removed in a future\n                            release. Prefer using `authorization`.\"\n                          type: string\n                        enableHttp2:\n                          description: Whether to enable HTTP2.\n                          type: boolean\n                        name:\n                          description: Name of the Endpoints object in the namespace.\n                          type: string\n                        namespace:\n                          description: Namespace of the Endpoints object.\n                          type: string\n                        pathPrefix:\n                          description: Prefix for the HTTP path alerts are pushed\n                            to.\n                          type: string\n                        port:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          description: Port on which the Alertmanager API is exposed.\n                          x-kubernetes-int-or-string: true\n                        scheme:\n                          description: Scheme to use when firing alerts.\n                          type: string\n                        sigv4:\n                          description: \"Sigv4 allows to configures AWS's Signature\n                            Verification 4 for the URL. \\n It requires Prometheus\n                            >= v2.48.0. \\n Cannot be set at the same time as `basicAuth`,\n                            `bearerTokenFile` or `authorization`.\"\n                          properties:\n                            accessKey:\n                              description: AccessKey is the AWS API key. If not specified,\n                                the environment variable `AWS_ACCESS_KEY_ID` is used.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            profile:\n                              description: Profile is the named AWS profile used to\n                                authenticate.\n                              type: string\n                            region:\n                              description: Region is the AWS region. If blank, the\n                                region from the default credentials chain used.\n                              type: string\n                            roleArn:\n                              description: RoleArn is the named AWS profile used to\n                                authenticate.\n                              type: string\n                            secretKey:\n                              description: SecretKey is the AWS API secret. If not\n                                specified, the environment variable `AWS_SECRET_ACCESS_KEY`\n                                is used.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        timeout:\n                          description: Timeout is a per-target Alertmanager timeout\n                            when pushing alerts.\n                          pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                          type: string\n                        tlsConfig:\n                          description: TLS Config to use for Alertmanager.\n                          properties:\n                            ca:\n                              description: Certificate authority used when verifying\n                                server certificates.\n                              properties:\n                                configMap:\n                                  description: ConfigMap containing data to use for\n                                    the targets.\n                                  properties:\n                                    key:\n                                      description: The key to select.\n                                      type: string\n                                    name:\n                                      description: 'Name of the referent. More info:\n                                        https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                        TODO: Add other useful fields. apiVersion,\n                                        kind, uid?'\n                                      type: string\n                                    optional:\n                                      description: Specify whether the ConfigMap or\n                                        its key must be defined\n                                      type: boolean\n                                  required:\n                                  - key\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                secret:\n                                  description: Secret containing data to use for the\n                                    targets.\n                                  properties:\n                                    key:\n                                      description: The key of the secret to select\n                                        from.  Must be a valid secret key.\n                                      type: string\n                                    name:\n                                      description: 'Name of the referent. More info:\n                                        https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                        TODO: Add other useful fields. apiVersion,\n                                        kind, uid?'\n                                      type: string\n                                    optional:\n                                      description: Specify whether the Secret or its\n                                        key must be defined\n                                      type: boolean\n                                  required:\n                                  - key\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                              type: object\n                            caFile:\n                              description: Path to the CA cert in the Prometheus container\n                                to use for the targets.\n                              type: string\n                            cert:\n                              description: Client certificate to present when doing\n                                client-authentication.\n                              properties:\n                                configMap:\n                                  description: ConfigMap containing data to use for\n                                    the targets.\n                                  properties:\n                                    key:\n                                      description: The key to select.\n                                      type: string\n                                    name:\n                                      description: 'Name of the referent. More info:\n                                        https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                        TODO: Add other useful fields. apiVersion,\n                                        kind, uid?'\n                                      type: string\n                                    optional:\n                                      description: Specify whether the ConfigMap or\n                                        its key must be defined\n                                      type: boolean\n                                  required:\n                                  - key\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                secret:\n                                  description: Secret containing data to use for the\n                                    targets.\n                                  properties:\n                                    key:\n                                      description: The key of the secret to select\n                                        from.  Must be a valid secret key.\n                                      type: string\n                                    name:\n                                      description: 'Name of the referent. More info:\n                                        https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                        TODO: Add other useful fields. apiVersion,\n                                        kind, uid?'\n                                      type: string\n                                    optional:\n                                      description: Specify whether the Secret or its\n                                        key must be defined\n                                      type: boolean\n                                  required:\n                                  - key\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                              type: object\n                            certFile:\n                              description: Path to the client cert file in the Prometheus\n                                container for the targets.\n                              type: string\n                            insecureSkipVerify:\n                              description: Disable target certificate validation.\n                              type: boolean\n                            keyFile:\n                              description: Path to the client key file in the Prometheus\n                                container for the targets.\n                              type: string\n                            keySecret:\n                              description: Secret containing the client key file for\n                                the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            serverName:\n                              description: Used to verify the hostname for the targets.\n                              type: string\n                          type: object\n                      required:\n                      - name\n                      - namespace\n                      - port\n                      type: object\n                    type: array\n                required:\n                - alertmanagers\n                type: object\n              allowOverlappingBlocks:\n                description: \"AllowOverlappingBlocks enables vertical compaction and\n                  vertical query merge in Prometheus. \\n Deprecated: this flag has\n                  no effect for Prometheus >= 2.39.0 where overlapping blocks are\n                  enabled by default.\"\n                type: boolean\n              apiserverConfig:\n                description: 'APIServerConfig allows specifying a host and auth methods\n                  to access the Kuberntees API server. If null, Prometheus is assumed\n                  to run inside of the cluster: it will discover the API servers automatically\n                  and use the Pod''s CA certificate and bearer token file at /var/run/secrets/kubernetes.io/serviceaccount/.'\n                properties:\n                  authorization:\n                    description: \"Authorization section for the API server. \\n Cannot\n                      be set at the same time as `basicAuth`, `bearerToken`, or `bearerTokenFile`.\"\n                    properties:\n                      credentials:\n                        description: Selects a key of a Secret in the namespace that\n                          contains the credentials for authentication.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      credentialsFile:\n                        description: File to read a secret from, mutually exclusive\n                          with `credentials`.\n                        type: string\n                      type:\n                        description: \"Defines the authentication type. The value is\n                          case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                          \\n Default: \\\"Bearer\\\"\"\n                        type: string\n                    type: object\n                  basicAuth:\n                    description: \"BasicAuth configuration for the API server. \\n Cannot\n                      be set at the same time as `authorization`, `bearerToken`, or\n                      `bearerTokenFile`.\"\n                    properties:\n                      password:\n                        description: '`password` specifies a key of a Secret containing\n                          the password for authentication.'\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      username:\n                        description: '`username` specifies a key of a Secret containing\n                          the username for authentication.'\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  bearerToken:\n                    description: \"*Warning: this field shouldn't be used because the\n                      token value appears in clear-text. Prefer using `authorization`.*\n                      \\n Deprecated: this will be removed in a future release.\"\n                    type: string\n                  bearerTokenFile:\n                    description: \"File to read bearer token for accessing apiserver.\n                      \\n Cannot be set at the same time as `basicAuth`, `authorization`,\n                      or `bearerToken`. \\n Deprecated: this will be removed in a future\n                      release. Prefer using `authorization`.\"\n                    type: string\n                  host:\n                    description: Kubernetes API address consisting of a hostname or\n                      IP address followed by an optional port number.\n                    type: string\n                  tlsConfig:\n                    description: TLS Config to use for the API server.\n                    properties:\n                      ca:\n                        description: Certificate authority used when verifying server\n                          certificates.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      caFile:\n                        description: Path to the CA cert in the Prometheus container\n                          to use for the targets.\n                        type: string\n                      cert:\n                        description: Client certificate to present when doing client-authentication.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      certFile:\n                        description: Path to the client cert file in the Prometheus\n                          container for the targets.\n                        type: string\n                      insecureSkipVerify:\n                        description: Disable target certificate validation.\n                        type: boolean\n                      keyFile:\n                        description: Path to the client key file in the Prometheus\n                          container for the targets.\n                        type: string\n                      keySecret:\n                        description: Secret containing the client key file for the\n                          targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      serverName:\n                        description: Used to verify the hostname for the targets.\n                        type: string\n                    type: object\n                required:\n                - host\n                type: object\n              arbitraryFSAccessThroughSMs:\n                description: When true, ServiceMonitor, PodMonitor and Probe object\n                  are forbidden to reference arbitrary files on the file system of\n                  the 'prometheus' container. When a ServiceMonitor's endpoint specifies\n                  a `bearerTokenFile` value (e.g.  '/var/run/secrets/kubernetes.io/serviceaccount/token'),\n                  a malicious target can get access to the Prometheus service account's\n                  token in the Prometheus' scrape request. Setting `spec.arbitraryFSAccessThroughSM`\n                  to 'true' would prevent the attack. Users should instead provide\n                  the credentials using the `spec.bearerTokenSecret` field.\n                properties:\n                  deny:\n                    type: boolean\n                type: object\n              baseImage:\n                description: 'Deprecated: use ''spec.image'' instead.'\n                type: string\n              bodySizeLimit:\n                description: BodySizeLimit defines per-scrape on response body size.\n                  Only valid in Prometheus versions 2.45.0 and newer.\n                pattern: (^0|([0-9]*[.])?[0-9]+((K|M|G|T|E|P)i?)?B)$\n                type: string\n              configMaps:\n                description: ConfigMaps is a list of ConfigMaps in the same namespace\n                  as the Prometheus object, which shall be mounted into the Prometheus\n                  Pods. Each ConfigMap is added to the StatefulSet definition as a\n                  volume named `configmap-<configmap-name>`. The ConfigMaps are mounted\n                  into /etc/prometheus/configmaps/<configmap-name> in the 'prometheus'\n                  container.\n                items:\n                  type: string\n                type: array\n              containers:\n                description: \"Containers allows injecting additional containers or\n                  modifying operator generated containers. This can be used to allow\n                  adding an authentication proxy to the Pods or to change the behavior\n                  of an operator generated container. Containers described here modify\n                  an operator generated container if they share the same name and\n                  modifications are done via a strategic merge patch. \\n The names\n                  of containers managed by the operator are: * `prometheus` * `config-reloader`\n                  * `thanos-sidecar` \\n Overriding containers is entirely outside\n                  the scope of what the maintainers will support and by doing so,\n                  you accept that this behaviour may break at any time without notice.\"\n                items:\n                  description: A single application container that you want to run\n                    within a pod.\n                  properties:\n                    args:\n                      description: 'Arguments to the entrypoint. The container image''s\n                        CMD is used if this is not provided. Variable references $(VAR_NAME)\n                        are expanded using the container''s environment. If a variable\n                        cannot be resolved, the reference in the input string will\n                        be unchanged. Double $$ are reduced to a single $, which allows\n                        for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\n                        produce the string literal \"$(VAR_NAME)\". Escaped references\n                        will never be expanded, regardless of whether the variable\n                        exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    command:\n                      description: 'Entrypoint array. Not executed within a shell.\n                        The container image''s ENTRYPOINT is used if this is not provided.\n                        Variable references $(VAR_NAME) are expanded using the container''s\n                        environment. If a variable cannot be resolved, the reference\n                        in the input string will be unchanged. Double $$ are reduced\n                        to a single $, which allows for escaping the $(VAR_NAME) syntax:\n                        i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\n                        Escaped references will never be expanded, regardless of whether\n                        the variable exists or not. Cannot be updated. More info:\n                        https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    env:\n                      description: List of environment variables to set in the container.\n                        Cannot be updated.\n                      items:\n                        description: EnvVar represents an environment variable present\n                          in a Container.\n                        properties:\n                          name:\n                            description: Name of the environment variable. Must be\n                              a C_IDENTIFIER.\n                            type: string\n                          value:\n                            description: 'Variable references $(VAR_NAME) are expanded\n                              using the previously defined environment variables in\n                              the container and any service environment variables.\n                              If a variable cannot be resolved, the reference in the\n                              input string will be unchanged. Double $$ are reduced\n                              to a single $, which allows for escaping the $(VAR_NAME)\n                              syntax: i.e. \"$$(VAR_NAME)\" will produce the string\n                              literal \"$(VAR_NAME)\". Escaped references will never\n                              be expanded, regardless of whether the variable exists\n                              or not. Defaults to \"\".'\n                            type: string\n                          valueFrom:\n                            description: Source for the environment variable's value.\n                              Cannot be used if value is not empty.\n                            properties:\n                              configMapKeyRef:\n                                description: Selects a key of a ConfigMap.\n                                properties:\n                                  key:\n                                    description: The key to select.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              fieldRef:\n                                description: 'Selects a field of the pod: supports\n                                  metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`,\n                                  `metadata.annotations[''<KEY>'']`, spec.nodeName,\n                                  spec.serviceAccountName, status.hostIP, status.podIP,\n                                  status.podIPs.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, limits.ephemeral-storage, requests.cpu,\n                                  requests.memory and requests.ephemeral-storage)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              secretKeyRef:\n                                description: Selects a key of a secret in the pod's\n                                  namespace\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    envFrom:\n                      description: List of sources to populate environment variables\n                        in the container. The keys defined within a source must be\n                        a C_IDENTIFIER. All invalid keys will be reported as an event\n                        when the container is starting. When a key exists in multiple\n                        sources, the value associated with the last source will take\n                        precedence. Values defined by an Env with a duplicate key\n                        will take precedence. Cannot be updated.\n                      items:\n                        description: EnvFromSource represents the source of a set\n                          of ConfigMaps\n                        properties:\n                          configMapRef:\n                            description: The ConfigMap to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap must be\n                                  defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          prefix:\n                            description: An optional identifier to prepend to each\n                              key in the ConfigMap. Must be a C_IDENTIFIER.\n                            type: string\n                          secretRef:\n                            description: The Secret to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret must be defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    image:\n                      description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                        This field is optional to allow higher level config management\n                        to default or override container images in workload controllers\n                        like Deployments and StatefulSets.'\n                      type: string\n                    imagePullPolicy:\n                      description: 'Image pull policy. One of Always, Never, IfNotPresent.\n                        Defaults to Always if :latest tag is specified, or IfNotPresent\n                        otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                      type: string\n                    lifecycle:\n                      description: Actions that the management system should take\n                        in response to container lifecycle events. Cannot be updated.\n                      properties:\n                        postStart:\n                          description: 'PostStart is called immediately after a container\n                            is created. If the handler fails, the container is terminated\n                            and restarted according to its restart policy. Other management\n                            of the container blocks until the hook completes. More\n                            info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                        preStop:\n                          description: 'PreStop is called immediately before a container\n                            is terminated due to an API request or management event\n                            such as liveness/startup probe failure, preemption, resource\n                            contention, etc. The handler is not called if the container\n                            crashes or exits. The Pod''s termination grace period\n                            countdown begins before the PreStop hook is executed.\n                            Regardless of the outcome of the handler, the container\n                            will eventually terminate within the Pod''s termination\n                            grace period (unless delayed by finalizers). Other management\n                            of the container blocks until the hook completes or until\n                            the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                      type: object\n                    livenessProbe:\n                      description: 'Periodic probe of container liveness. Container\n                        will be restarted if the probe fails. Cannot be updated. More\n                        info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    name:\n                      description: Name of the container specified as a DNS_LABEL.\n                        Each container in a pod must have a unique name (DNS_LABEL).\n                        Cannot be updated.\n                      type: string\n                    ports:\n                      description: List of ports to expose from the container. Not\n                        specifying a port here DOES NOT prevent that port from being\n                        exposed. Any port which is listening on the default \"0.0.0.0\"\n                        address inside a container will be accessible from the network.\n                        Modifying this array with strategic merge patch may corrupt\n                        the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255.\n                        Cannot be updated.\n                      items:\n                        description: ContainerPort represents a network port in a\n                          single container.\n                        properties:\n                          containerPort:\n                            description: Number of port to expose on the pod's IP\n                              address. This must be a valid port number, 0 < x < 65536.\n                            format: int32\n                            type: integer\n                          hostIP:\n                            description: What host IP to bind the external port to.\n                            type: string\n                          hostPort:\n                            description: Number of port to expose on the host. If\n                              specified, this must be a valid port number, 0 < x <\n                              65536. If HostNetwork is specified, this must match\n                              ContainerPort. Most containers do not need this.\n                            format: int32\n                            type: integer\n                          name:\n                            description: If specified, this must be an IANA_SVC_NAME\n                              and unique within the pod. Each named port in a pod\n                              must have a unique name. Name for the port that can\n                              be referred to by services.\n                            type: string\n                          protocol:\n                            default: TCP\n                            description: Protocol for port. Must be UDP, TCP, or SCTP.\n                              Defaults to \"TCP\".\n                            type: string\n                        required:\n                        - containerPort\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - containerPort\n                      - protocol\n                      x-kubernetes-list-type: map\n                    readinessProbe:\n                      description: 'Periodic probe of container service readiness.\n                        Container will be removed from service endpoints if the probe\n                        fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    resizePolicy:\n                      description: Resources resize policy for the container.\n                      items:\n                        description: ContainerResizePolicy represents resource resize\n                          policy for the container.\n                        properties:\n                          resourceName:\n                            description: 'Name of the resource to which this resource\n                              resize policy applies. Supported values: cpu, memory.'\n                            type: string\n                          restartPolicy:\n                            description: Restart policy to apply when specified resource\n                              is resized. If not specified, it defaults to NotRequired.\n                            type: string\n                        required:\n                        - resourceName\n                        - restartPolicy\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    resources:\n                      description: 'Compute Resources required by this container.\n                        Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                      properties:\n                        claims:\n                          description: \"Claims lists the names of resources, defined\n                            in spec.resourceClaims, that are used by this container.\n                            \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                            feature gate. \\n This field is immutable. It can only\n                            be set for containers.\"\n                          items:\n                            description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                            properties:\n                              name:\n                                description: Name must match the name of one entry\n                                  in pod.spec.resourceClaims of the Pod where this\n                                  field is used. It makes that resource available\n                                  inside a container.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - name\n                          x-kubernetes-list-type: map\n                        limits:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Limits describes the maximum amount of compute\n                            resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                        requests:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Requests describes the minimum amount of compute\n                            resources required. If Requests is omitted for a container,\n                            it defaults to Limits if that is explicitly specified,\n                            otherwise to an implementation-defined value. Requests\n                            cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                      type: object\n                    restartPolicy:\n                      description: 'RestartPolicy defines the restart behavior of\n                        individual containers in a pod. This field may only be set\n                        for init containers, and the only allowed value is \"Always\".\n                        For non-init containers or when this field is not specified,\n                        the restart behavior is defined by the Pod''s restart policy\n                        and the container type. Setting the RestartPolicy as \"Always\"\n                        for the init container will have the following effect: this\n                        init container will be continually restarted on exit until\n                        all regular containers have terminated. Once all regular containers\n                        have completed, all init containers with restartPolicy \"Always\"\n                        will be shut down. This lifecycle differs from normal init\n                        containers and is often referred to as a \"sidecar\" container.\n                        Although this init container still starts in the init container\n                        sequence, it does not wait for the container to complete before\n                        proceeding to the next init container. Instead, the next init\n                        container starts immediately after this init container is\n                        started, or after any startupProbe has successfully completed.'\n                      type: string\n                    securityContext:\n                      description: 'SecurityContext defines the security options the\n                        container should be run with. If set, the fields of SecurityContext\n                        override the equivalent fields of PodSecurityContext. More\n                        info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                      properties:\n                        allowPrivilegeEscalation:\n                          description: 'AllowPrivilegeEscalation controls whether\n                            a process can gain more privileges than its parent process.\n                            This bool directly controls if the no_new_privs flag will\n                            be set on the container process. AllowPrivilegeEscalation\n                            is true always when the container is: 1) run as Privileged\n                            2) has CAP_SYS_ADMIN Note that this field cannot be set\n                            when spec.os.name is windows.'\n                          type: boolean\n                        capabilities:\n                          description: The capabilities to add/drop when running containers.\n                            Defaults to the default set of capabilities granted by\n                            the container runtime. Note that this field cannot be\n                            set when spec.os.name is windows.\n                          properties:\n                            add:\n                              description: Added capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                            drop:\n                              description: Removed capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                          type: object\n                        privileged:\n                          description: Run container in privileged mode. Processes\n                            in privileged containers are essentially equivalent to\n                            root on the host. Defaults to false. Note that this field\n                            cannot be set when spec.os.name is windows.\n                          type: boolean\n                        procMount:\n                          description: procMount denotes the type of proc mount to\n                            use for the containers. The default is DefaultProcMount\n                            which uses the container runtime defaults for readonly\n                            paths and masked paths. This requires the ProcMountType\n                            feature flag to be enabled. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: string\n                        readOnlyRootFilesystem:\n                          description: Whether this container has a read-only root\n                            filesystem. Default is false. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: boolean\n                        runAsGroup:\n                          description: The GID to run the entrypoint of the container\n                            process. Uses runtime default if unset. May also be set\n                            in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        runAsNonRoot:\n                          description: Indicates that the container must run as a\n                            non-root user. If true, the Kubelet will validate the\n                            image at runtime to ensure that it does not run as UID\n                            0 (root) and fail to start the container if it does. If\n                            unset or false, no such validation will be performed.\n                            May also be set in PodSecurityContext.  If set in both\n                            SecurityContext and PodSecurityContext, the value specified\n                            in SecurityContext takes precedence.\n                          type: boolean\n                        runAsUser:\n                          description: The UID to run the entrypoint of the container\n                            process. Defaults to user specified in image metadata\n                            if unspecified. May also be set in PodSecurityContext.  If\n                            set in both SecurityContext and PodSecurityContext, the\n                            value specified in SecurityContext takes precedence. Note\n                            that this field cannot be set when spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        seLinuxOptions:\n                          description: The SELinux context to be applied to the container.\n                            If unspecified, the container runtime will allocate a\n                            random SELinux context for each container.  May also be\n                            set in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          properties:\n                            level:\n                              description: Level is SELinux level label that applies\n                                to the container.\n                              type: string\n                            role:\n                              description: Role is a SELinux role label that applies\n                                to the container.\n                              type: string\n                            type:\n                              description: Type is a SELinux type label that applies\n                                to the container.\n                              type: string\n                            user:\n                              description: User is a SELinux user label that applies\n                                to the container.\n                              type: string\n                          type: object\n                        seccompProfile:\n                          description: The seccomp options to use by this container.\n                            If seccomp options are provided at both the pod & container\n                            level, the container options override the pod options.\n                            Note that this field cannot be set when spec.os.name is\n                            windows.\n                          properties:\n                            localhostProfile:\n                              description: localhostProfile indicates a profile defined\n                                in a file on the node should be used. The profile\n                                must be preconfigured on the node to work. Must be\n                                a descending path, relative to the kubelet's configured\n                                seccomp profile location. Must be set if type is \"Localhost\".\n                                Must NOT be set for any other type.\n                              type: string\n                            type:\n                              description: \"type indicates which kind of seccomp profile\n                                will be applied. Valid options are: \\n Localhost -\n                                a profile defined in a file on the node should be\n                                used. RuntimeDefault - the container runtime default\n                                profile should be used. Unconfined - no profile should\n                                be applied.\"\n                              type: string\n                          required:\n                          - type\n                          type: object\n                        windowsOptions:\n                          description: The Windows specific settings applied to all\n                            containers. If unspecified, the options from the PodSecurityContext\n                            will be used. If set in both SecurityContext and PodSecurityContext,\n                            the value specified in SecurityContext takes precedence.\n                            Note that this field cannot be set when spec.os.name is\n                            linux.\n                          properties:\n                            gmsaCredentialSpec:\n                              description: GMSACredentialSpec is where the GMSA admission\n                                webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                inlines the contents of the GMSA credential spec named\n                                by the GMSACredentialSpecName field.\n                              type: string\n                            gmsaCredentialSpecName:\n                              description: GMSACredentialSpecName is the name of the\n                                GMSA credential spec to use.\n                              type: string\n                            hostProcess:\n                              description: HostProcess determines if a container should\n                                be run as a 'Host Process' container. All of a Pod's\n                                containers must have the same effective HostProcess\n                                value (it is not allowed to have a mix of HostProcess\n                                containers and non-HostProcess containers). In addition,\n                                if HostProcess is true then HostNetwork must also\n                                be set to true.\n                              type: boolean\n                            runAsUserName:\n                              description: The UserName in Windows to run the entrypoint\n                                of the container process. Defaults to the user specified\n                                in image metadata if unspecified. May also be set\n                                in PodSecurityContext. If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence.\n                              type: string\n                          type: object\n                      type: object\n                    startupProbe:\n                      description: 'StartupProbe indicates that the Pod has successfully\n                        initialized. If specified, no other probes are executed until\n                        this completes successfully. If this probe fails, the Pod\n                        will be restarted, just as if the livenessProbe failed. This\n                        can be used to provide different probe parameters at the beginning\n                        of a Pod''s lifecycle, when it might take a long time to load\n                        data or warm a cache, than during steady-state operation.\n                        This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    stdin:\n                      description: Whether this container should allocate a buffer\n                        for stdin in the container runtime. If this is not set, reads\n                        from stdin in the container will always result in EOF. Default\n                        is false.\n                      type: boolean\n                    stdinOnce:\n                      description: Whether the container runtime should close the\n                        stdin channel after it has been opened by a single attach.\n                        When stdin is true the stdin stream will remain open across\n                        multiple attach sessions. If stdinOnce is set to true, stdin\n                        is opened on container start, is empty until the first client\n                        attaches to stdin, and then remains open and accepts data\n                        until the client disconnects, at which time stdin is closed\n                        and remains closed until the container is restarted. If this\n                        flag is false, a container processes that reads from stdin\n                        will never receive an EOF. Default is false\n                      type: boolean\n                    terminationMessagePath:\n                      description: 'Optional: Path at which the file to which the\n                        container''s termination message will be written is mounted\n                        into the container''s filesystem. Message written is intended\n                        to be brief final status, such as an assertion failure message.\n                        Will be truncated by the node if greater than 4096 bytes.\n                        The total message length across all containers will be limited\n                        to 12kb. Defaults to /dev/termination-log. Cannot be updated.'\n                      type: string\n                    terminationMessagePolicy:\n                      description: Indicate how the termination message should be\n                        populated. File will use the contents of terminationMessagePath\n                        to populate the container status message on both success and\n                        failure. FallbackToLogsOnError will use the last chunk of\n                        container log output if the termination message file is empty\n                        and the container exited with an error. The log output is\n                        limited to 2048 bytes or 80 lines, whichever is smaller. Defaults\n                        to File. Cannot be updated.\n                      type: string\n                    tty:\n                      description: Whether this container should allocate a TTY for\n                        itself, also requires 'stdin' to be true. Default is false.\n                      type: boolean\n                    volumeDevices:\n                      description: volumeDevices is the list of block devices to be\n                        used by the container.\n                      items:\n                        description: volumeDevice describes a mapping of a raw block\n                          device within a container.\n                        properties:\n                          devicePath:\n                            description: devicePath is the path inside of the container\n                              that the device will be mapped to.\n                            type: string\n                          name:\n                            description: name must match the name of a persistentVolumeClaim\n                              in the pod\n                            type: string\n                        required:\n                        - devicePath\n                        - name\n                        type: object\n                      type: array\n                    volumeMounts:\n                      description: Pod volumes to mount into the container's filesystem.\n                        Cannot be updated.\n                      items:\n                        description: VolumeMount describes a mounting of a Volume\n                          within a container.\n                        properties:\n                          mountPath:\n                            description: Path within the container at which the volume\n                              should be mounted.  Must not contain ':'.\n                            type: string\n                          mountPropagation:\n                            description: mountPropagation determines how mounts are\n                              propagated from the host to container and the other\n                              way around. When not set, MountPropagationNone is used.\n                              This field is beta in 1.10.\n                            type: string\n                          name:\n                            description: This must match the Name of a Volume.\n                            type: string\n                          readOnly:\n                            description: Mounted read-only if true, read-write otherwise\n                              (false or unspecified). Defaults to false.\n                            type: boolean\n                          subPath:\n                            description: Path within the volume from which the container's\n                              volume should be mounted. Defaults to \"\" (volume's root).\n                            type: string\n                          subPathExpr:\n                            description: Expanded path within the volume from which\n                              the container's volume should be mounted. Behaves similarly\n                              to SubPath but environment variable references $(VAR_NAME)\n                              are expanded using the container's environment. Defaults\n                              to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                              exclusive.\n                            type: string\n                        required:\n                        - mountPath\n                        - name\n                        type: object\n                      type: array\n                    workingDir:\n                      description: Container's working directory. If not specified,\n                        the container runtime's default will be used, which might\n                        be configured in the container image. Cannot be updated.\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              disableCompaction:\n                description: When true, the Prometheus compaction is disabled.\n                type: boolean\n              enableAdminAPI:\n                description: \"Enables access to the Prometheus web admin API. \\n WARNING:\n                  Enabling the admin APIs enables mutating endpoints, to delete data,\n                  shutdown Prometheus, and more. Enabling this should be done with\n                  care and the user is advised to add additional authentication authorization\n                  via a proxy to ensure only clients authorized to perform these actions\n                  can do so. \\n For more information: https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-admin-apis\"\n                type: boolean\n              enableFeatures:\n                description: \"Enable access to Prometheus feature flags. By default,\n                  no features are enabled. \\n Enabling features which are disabled\n                  by default is entirely outside the scope of what the maintainers\n                  will support and by doing so, you accept that this behaviour may\n                  break at any time without notice. \\n For more information see https://prometheus.io/docs/prometheus/latest/feature_flags/\"\n                items:\n                  type: string\n                type: array\n              enableRemoteWriteReceiver:\n                description: \"Enable Prometheus to be used as a receiver for the Prometheus\n                  remote write protocol. \\n WARNING: This is not considered an efficient\n                  way of ingesting samples. Use it with caution for specific low-volume\n                  use cases. It is not suitable for replacing the ingestion via scraping\n                  and turning Prometheus into a push-based metrics collection system.\n                  For more information see https://prometheus.io/docs/prometheus/latest/querying/api/#remote-write-receiver\n                  \\n It requires Prometheus >= v2.33.0.\"\n                type: boolean\n              enforcedBodySizeLimit:\n                description: \"When defined, enforcedBodySizeLimit specifies a global\n                  limit on the size of uncompressed response body that will be accepted\n                  by Prometheus. Targets responding with a body larger than this many\n                  bytes will cause the scrape to fail. \\n It requires Prometheus >=\n                  v2.28.0.\"\n                pattern: (^0|([0-9]*[.])?[0-9]+((K|M|G|T|E|P)i?)?B)$\n                type: string\n              enforcedKeepDroppedTargets:\n                description: \"When defined, enforcedKeepDroppedTargets specifies a\n                  global limit on the number of targets dropped by relabeling that\n                  will be kept in memory. The value overrides any `spec.keepDroppedTargets`\n                  set by ServiceMonitor, PodMonitor, Probe objects unless `spec.keepDroppedTargets`\n                  is greater than zero and less than `spec.enforcedKeepDroppedTargets`.\n                  \\n It requires Prometheus >= v2.47.0.\"\n                format: int64\n                type: integer\n              enforcedLabelLimit:\n                description: \"When defined, enforcedLabelLimit specifies a global\n                  limit on the number of labels per sample. The value overrides any\n                  `spec.labelLimit` set by ServiceMonitor, PodMonitor, Probe objects\n                  unless `spec.labelLimit` is greater than zero and less than `spec.enforcedLabelLimit`.\n                  \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              enforcedLabelNameLengthLimit:\n                description: \"When defined, enforcedLabelNameLengthLimit specifies\n                  a global limit on the length of labels name per sample. The value\n                  overrides any `spec.labelNameLengthLimit` set by ServiceMonitor,\n                  PodMonitor, Probe objects unless `spec.labelNameLengthLimit` is\n                  greater than zero and less than `spec.enforcedLabelNameLengthLimit`.\n                  \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              enforcedLabelValueLengthLimit:\n                description: \"When not null, enforcedLabelValueLengthLimit defines\n                  a global limit on the length of labels value per sample. The value\n                  overrides any `spec.labelValueLengthLimit` set by ServiceMonitor,\n                  PodMonitor, Probe objects unless `spec.labelValueLengthLimit` is\n                  greater than zero and less than `spec.enforcedLabelValueLengthLimit`.\n                  \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              enforcedNamespaceLabel:\n                description: \"When not empty, a label will be added to \\n 1. All metrics\n                  scraped from `ServiceMonitor`, `PodMonitor`, `Probe` and `ScrapeConfig`\n                  objects. 2. All metrics generated from recording rules defined in\n                  `PrometheusRule` objects. 3. All alerts generated from alerting\n                  rules defined in `PrometheusRule` objects. 4. All vector selectors\n                  of PromQL expressions defined in `PrometheusRule` objects. \\n The\n                  label will not added for objects referenced in `spec.excludedFromEnforcement`.\n                  \\n The label's name is this field's value. The label's value is\n                  the namespace of the `ServiceMonitor`, `PodMonitor`, `Probe` or\n                  `PrometheusRule` object.\"\n                type: string\n              enforcedSampleLimit:\n                description: \"When defined, enforcedSampleLimit specifies a global\n                  limit on the number of scraped samples that will be accepted. This\n                  overrides any `spec.sampleLimit` set by ServiceMonitor, PodMonitor,\n                  Probe objects unless `spec.sampleLimit` is greater than zero and\n                  less than than `spec.enforcedSampleLimit`. \\n It is meant to be\n                  used by admins to keep the overall number of samples/series under\n                  a desired limit.\"\n                format: int64\n                type: integer\n              enforcedTargetLimit:\n                description: \"When defined, enforcedTargetLimit specifies a global\n                  limit on the number of scraped targets. The value overrides any\n                  `spec.targetLimit` set by ServiceMonitor, PodMonitor, Probe objects\n                  unless `spec.targetLimit` is greater than zero and less than `spec.enforcedTargetLimit`.\n                  \\n It is meant to be used by admins to to keep the overall number\n                  of targets under a desired limit.\"\n                format: int64\n                type: integer\n              evaluationInterval:\n                default: 30s\n                description: 'Interval between rule evaluations. Default: \"30s\"'\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              excludedFromEnforcement:\n                description: \"List of references to PodMonitor, ServiceMonitor, Probe\n                  and PrometheusRule objects to be excluded from enforcing a namespace\n                  label of origin. \\n It is only applicable if `spec.enforcedNamespaceLabel`\n                  set to true.\"\n                items:\n                  description: ObjectReference references a PodMonitor, ServiceMonitor,\n                    Probe or PrometheusRule object.\n                  properties:\n                    group:\n                      default: monitoring.coreos.com\n                      description: Group of the referent. When not specified, it defaults\n                        to `monitoring.coreos.com`\n                      enum:\n                      - monitoring.coreos.com\n                      type: string\n                    name:\n                      description: Name of the referent. When not set, all resources\n                        in the namespace are matched.\n                      type: string\n                    namespace:\n                      description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'\n                      minLength: 1\n                      type: string\n                    resource:\n                      description: Resource of the referent.\n                      enum:\n                      - prometheusrules\n                      - servicemonitors\n                      - podmonitors\n                      - probes\n                      - scrapeconfigs\n                      type: string\n                  required:\n                  - namespace\n                  - resource\n                  type: object\n                type: array\n              exemplars:\n                description: Exemplars related settings that are runtime reloadable.\n                  It requires to enable the `exemplar-storage` feature flag to be\n                  effective.\n                properties:\n                  maxSize:\n                    description: \"Maximum number of exemplars stored in memory for\n                      all series. \\n exemplar-storage itself must be enabled using\n                      the `spec.enableFeature` option for exemplars to be scraped\n                      in the first place. \\n If not set, Prometheus uses its default\n                      value. A value of zero or less than zero disables the storage.\"\n                    format: int64\n                    type: integer\n                type: object\n              externalLabels:\n                additionalProperties:\n                  type: string\n                description: The labels to add to any time series or alerts when communicating\n                  with external systems (federation, remote storage, Alertmanager).\n                  Labels defined by `spec.replicaExternalLabelName` and `spec.prometheusExternalLabelName`\n                  take precedence over this list.\n                type: object\n              externalUrl:\n                description: The external URL under which the Prometheus service is\n                  externally available. This is necessary to generate correct URLs\n                  (for instance if Prometheus is accessible behind an Ingress resource).\n                type: string\n              hostAliases:\n                description: Optional list of hosts and IPs that will be injected\n                  into the Pod's hosts file if specified.\n                items:\n                  description: HostAlias holds the mapping between IP and hostnames\n                    that will be injected as an entry in the pod's hosts file.\n                  properties:\n                    hostnames:\n                      description: Hostnames for the above IP address.\n                      items:\n                        type: string\n                      type: array\n                    ip:\n                      description: IP address of the host file entry.\n                      type: string\n                  required:\n                  - hostnames\n                  - ip\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - ip\n                x-kubernetes-list-type: map\n              hostNetwork:\n                description: \"Use the host's network namespace if true. \\n Make sure\n                  to understand the security implications if you want to enable it\n                  (https://kubernetes.io/docs/concepts/configuration/overview/). \\n\n                  When hostNetwork is enabled, this will set the DNS policy to `ClusterFirstWithHostNet`\n                  automatically.\"\n                type: boolean\n              ignoreNamespaceSelectors:\n                description: When true, `spec.namespaceSelector` from all PodMonitor,\n                  ServiceMonitor and Probe objects will be ignored. They will only\n                  discover targets within the namespace of the PodMonitor, ServiceMonitor\n                  and Probe object.\n                type: boolean\n              image:\n                description: \"Container image name for Prometheus. If specified, it\n                  takes precedence over the `spec.baseImage`, `spec.tag` and `spec.sha`\n                  fields. \\n Specifying `spec.version` is still necessary to ensure\n                  the Prometheus Operator knows which version of Prometheus is being\n                  configured. \\n If neither `spec.image` nor `spec.baseImage` are\n                  defined, the operator will use the latest upstream version of Prometheus\n                  available at the time when the operator was released.\"\n                type: string\n              imagePullPolicy:\n                description: Image pull policy for the 'prometheus', 'init-config-reloader'\n                  and 'config-reloader' containers. See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy\n                  for more details.\n                enum:\n                - \"\"\n                - Always\n                - Never\n                - IfNotPresent\n                type: string\n              imagePullSecrets:\n                description: An optional list of references to Secrets in the same\n                  namespace to use for pulling images from registries. See http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod\n                items:\n                  description: LocalObjectReference contains enough information to\n                    let you locate the referenced object inside the same namespace.\n                  properties:\n                    name:\n                      description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                        TODO: Add other useful fields. apiVersion, kind, uid?'\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                type: array\n              initContainers:\n                description: \"InitContainers allows injecting initContainers to the\n                  Pod definition. Those can be used to e.g.  fetch secrets for injection\n                  into the Prometheus configuration from external sources. Any errors\n                  during the execution of an initContainer will lead to a restart\n                  of the Pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/\n                  InitContainers described here modify an operator generated init\n                  containers if they share the same name and modifications are done\n                  via a strategic merge patch. \\n The names of init container name\n                  managed by the operator are: * `init-config-reloader`. \\n Overriding\n                  init containers is entirely outside the scope of what the maintainers\n                  will support and by doing so, you accept that this behaviour may\n                  break at any time without notice.\"\n                items:\n                  description: A single application container that you want to run\n                    within a pod.\n                  properties:\n                    args:\n                      description: 'Arguments to the entrypoint. The container image''s\n                        CMD is used if this is not provided. Variable references $(VAR_NAME)\n                        are expanded using the container''s environment. If a variable\n                        cannot be resolved, the reference in the input string will\n                        be unchanged. Double $$ are reduced to a single $, which allows\n                        for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\n                        produce the string literal \"$(VAR_NAME)\". Escaped references\n                        will never be expanded, regardless of whether the variable\n                        exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    command:\n                      description: 'Entrypoint array. Not executed within a shell.\n                        The container image''s ENTRYPOINT is used if this is not provided.\n                        Variable references $(VAR_NAME) are expanded using the container''s\n                        environment. If a variable cannot be resolved, the reference\n                        in the input string will be unchanged. Double $$ are reduced\n                        to a single $, which allows for escaping the $(VAR_NAME) syntax:\n                        i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\n                        Escaped references will never be expanded, regardless of whether\n                        the variable exists or not. Cannot be updated. More info:\n                        https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    env:\n                      description: List of environment variables to set in the container.\n                        Cannot be updated.\n                      items:\n                        description: EnvVar represents an environment variable present\n                          in a Container.\n                        properties:\n                          name:\n                            description: Name of the environment variable. Must be\n                              a C_IDENTIFIER.\n                            type: string\n                          value:\n                            description: 'Variable references $(VAR_NAME) are expanded\n                              using the previously defined environment variables in\n                              the container and any service environment variables.\n                              If a variable cannot be resolved, the reference in the\n                              input string will be unchanged. Double $$ are reduced\n                              to a single $, which allows for escaping the $(VAR_NAME)\n                              syntax: i.e. \"$$(VAR_NAME)\" will produce the string\n                              literal \"$(VAR_NAME)\". Escaped references will never\n                              be expanded, regardless of whether the variable exists\n                              or not. Defaults to \"\".'\n                            type: string\n                          valueFrom:\n                            description: Source for the environment variable's value.\n                              Cannot be used if value is not empty.\n                            properties:\n                              configMapKeyRef:\n                                description: Selects a key of a ConfigMap.\n                                properties:\n                                  key:\n                                    description: The key to select.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              fieldRef:\n                                description: 'Selects a field of the pod: supports\n                                  metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`,\n                                  `metadata.annotations[''<KEY>'']`, spec.nodeName,\n                                  spec.serviceAccountName, status.hostIP, status.podIP,\n                                  status.podIPs.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, limits.ephemeral-storage, requests.cpu,\n                                  requests.memory and requests.ephemeral-storage)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              secretKeyRef:\n                                description: Selects a key of a secret in the pod's\n                                  namespace\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    envFrom:\n                      description: List of sources to populate environment variables\n                        in the container. The keys defined within a source must be\n                        a C_IDENTIFIER. All invalid keys will be reported as an event\n                        when the container is starting. When a key exists in multiple\n                        sources, the value associated with the last source will take\n                        precedence. Values defined by an Env with a duplicate key\n                        will take precedence. Cannot be updated.\n                      items:\n                        description: EnvFromSource represents the source of a set\n                          of ConfigMaps\n                        properties:\n                          configMapRef:\n                            description: The ConfigMap to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap must be\n                                  defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          prefix:\n                            description: An optional identifier to prepend to each\n                              key in the ConfigMap. Must be a C_IDENTIFIER.\n                            type: string\n                          secretRef:\n                            description: The Secret to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret must be defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    image:\n                      description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                        This field is optional to allow higher level config management\n                        to default or override container images in workload controllers\n                        like Deployments and StatefulSets.'\n                      type: string\n                    imagePullPolicy:\n                      description: 'Image pull policy. One of Always, Never, IfNotPresent.\n                        Defaults to Always if :latest tag is specified, or IfNotPresent\n                        otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                      type: string\n                    lifecycle:\n                      description: Actions that the management system should take\n                        in response to container lifecycle events. Cannot be updated.\n                      properties:\n                        postStart:\n                          description: 'PostStart is called immediately after a container\n                            is created. If the handler fails, the container is terminated\n                            and restarted according to its restart policy. Other management\n                            of the container blocks until the hook completes. More\n                            info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                        preStop:\n                          description: 'PreStop is called immediately before a container\n                            is terminated due to an API request or management event\n                            such as liveness/startup probe failure, preemption, resource\n                            contention, etc. The handler is not called if the container\n                            crashes or exits. The Pod''s termination grace period\n                            countdown begins before the PreStop hook is executed.\n                            Regardless of the outcome of the handler, the container\n                            will eventually terminate within the Pod''s termination\n                            grace period (unless delayed by finalizers). Other management\n                            of the container blocks until the hook completes or until\n                            the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                      type: object\n                    livenessProbe:\n                      description: 'Periodic probe of container liveness. Container\n                        will be restarted if the probe fails. Cannot be updated. More\n                        info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    name:\n                      description: Name of the container specified as a DNS_LABEL.\n                        Each container in a pod must have a unique name (DNS_LABEL).\n                        Cannot be updated.\n                      type: string\n                    ports:\n                      description: List of ports to expose from the container. Not\n                        specifying a port here DOES NOT prevent that port from being\n                        exposed. Any port which is listening on the default \"0.0.0.0\"\n                        address inside a container will be accessible from the network.\n                        Modifying this array with strategic merge patch may corrupt\n                        the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255.\n                        Cannot be updated.\n                      items:\n                        description: ContainerPort represents a network port in a\n                          single container.\n                        properties:\n                          containerPort:\n                            description: Number of port to expose on the pod's IP\n                              address. This must be a valid port number, 0 < x < 65536.\n                            format: int32\n                            type: integer\n                          hostIP:\n                            description: What host IP to bind the external port to.\n                            type: string\n                          hostPort:\n                            description: Number of port to expose on the host. If\n                              specified, this must be a valid port number, 0 < x <\n                              65536. If HostNetwork is specified, this must match\n                              ContainerPort. Most containers do not need this.\n                            format: int32\n                            type: integer\n                          name:\n                            description: If specified, this must be an IANA_SVC_NAME\n                              and unique within the pod. Each named port in a pod\n                              must have a unique name. Name for the port that can\n                              be referred to by services.\n                            type: string\n                          protocol:\n                            default: TCP\n                            description: Protocol for port. Must be UDP, TCP, or SCTP.\n                              Defaults to \"TCP\".\n                            type: string\n                        required:\n                        - containerPort\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - containerPort\n                      - protocol\n                      x-kubernetes-list-type: map\n                    readinessProbe:\n                      description: 'Periodic probe of container service readiness.\n                        Container will be removed from service endpoints if the probe\n                        fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    resizePolicy:\n                      description: Resources resize policy for the container.\n                      items:\n                        description: ContainerResizePolicy represents resource resize\n                          policy for the container.\n                        properties:\n                          resourceName:\n                            description: 'Name of the resource to which this resource\n                              resize policy applies. Supported values: cpu, memory.'\n                            type: string\n                          restartPolicy:\n                            description: Restart policy to apply when specified resource\n                              is resized. If not specified, it defaults to NotRequired.\n                            type: string\n                        required:\n                        - resourceName\n                        - restartPolicy\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    resources:\n                      description: 'Compute Resources required by this container.\n                        Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                      properties:\n                        claims:\n                          description: \"Claims lists the names of resources, defined\n                            in spec.resourceClaims, that are used by this container.\n                            \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                            feature gate. \\n This field is immutable. It can only\n                            be set for containers.\"\n                          items:\n                            description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                            properties:\n                              name:\n                                description: Name must match the name of one entry\n                                  in pod.spec.resourceClaims of the Pod where this\n                                  field is used. It makes that resource available\n                                  inside a container.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - name\n                          x-kubernetes-list-type: map\n                        limits:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Limits describes the maximum amount of compute\n                            resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                        requests:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Requests describes the minimum amount of compute\n                            resources required. If Requests is omitted for a container,\n                            it defaults to Limits if that is explicitly specified,\n                            otherwise to an implementation-defined value. Requests\n                            cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                      type: object\n                    restartPolicy:\n                      description: 'RestartPolicy defines the restart behavior of\n                        individual containers in a pod. This field may only be set\n                        for init containers, and the only allowed value is \"Always\".\n                        For non-init containers or when this field is not specified,\n                        the restart behavior is defined by the Pod''s restart policy\n                        and the container type. Setting the RestartPolicy as \"Always\"\n                        for the init container will have the following effect: this\n                        init container will be continually restarted on exit until\n                        all regular containers have terminated. Once all regular containers\n                        have completed, all init containers with restartPolicy \"Always\"\n                        will be shut down. This lifecycle differs from normal init\n                        containers and is often referred to as a \"sidecar\" container.\n                        Although this init container still starts in the init container\n                        sequence, it does not wait for the container to complete before\n                        proceeding to the next init container. Instead, the next init\n                        container starts immediately after this init container is\n                        started, or after any startupProbe has successfully completed.'\n                      type: string\n                    securityContext:\n                      description: 'SecurityContext defines the security options the\n                        container should be run with. If set, the fields of SecurityContext\n                        override the equivalent fields of PodSecurityContext. More\n                        info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                      properties:\n                        allowPrivilegeEscalation:\n                          description: 'AllowPrivilegeEscalation controls whether\n                            a process can gain more privileges than its parent process.\n                            This bool directly controls if the no_new_privs flag will\n                            be set on the container process. AllowPrivilegeEscalation\n                            is true always when the container is: 1) run as Privileged\n                            2) has CAP_SYS_ADMIN Note that this field cannot be set\n                            when spec.os.name is windows.'\n                          type: boolean\n                        capabilities:\n                          description: The capabilities to add/drop when running containers.\n                            Defaults to the default set of capabilities granted by\n                            the container runtime. Note that this field cannot be\n                            set when spec.os.name is windows.\n                          properties:\n                            add:\n                              description: Added capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                            drop:\n                              description: Removed capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                          type: object\n                        privileged:\n                          description: Run container in privileged mode. Processes\n                            in privileged containers are essentially equivalent to\n                            root on the host. Defaults to false. Note that this field\n                            cannot be set when spec.os.name is windows.\n                          type: boolean\n                        procMount:\n                          description: procMount denotes the type of proc mount to\n                            use for the containers. The default is DefaultProcMount\n                            which uses the container runtime defaults for readonly\n                            paths and masked paths. This requires the ProcMountType\n                            feature flag to be enabled. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: string\n                        readOnlyRootFilesystem:\n                          description: Whether this container has a read-only root\n                            filesystem. Default is false. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: boolean\n                        runAsGroup:\n                          description: The GID to run the entrypoint of the container\n                            process. Uses runtime default if unset. May also be set\n                            in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        runAsNonRoot:\n                          description: Indicates that the container must run as a\n                            non-root user. If true, the Kubelet will validate the\n                            image at runtime to ensure that it does not run as UID\n                            0 (root) and fail to start the container if it does. If\n                            unset or false, no such validation will be performed.\n                            May also be set in PodSecurityContext.  If set in both\n                            SecurityContext and PodSecurityContext, the value specified\n                            in SecurityContext takes precedence.\n                          type: boolean\n                        runAsUser:\n                          description: The UID to run the entrypoint of the container\n                            process. Defaults to user specified in image metadata\n                            if unspecified. May also be set in PodSecurityContext.  If\n                            set in both SecurityContext and PodSecurityContext, the\n                            value specified in SecurityContext takes precedence. Note\n                            that this field cannot be set when spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        seLinuxOptions:\n                          description: The SELinux context to be applied to the container.\n                            If unspecified, the container runtime will allocate a\n                            random SELinux context for each container.  May also be\n                            set in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          properties:\n                            level:\n                              description: Level is SELinux level label that applies\n                                to the container.\n                              type: string\n                            role:\n                              description: Role is a SELinux role label that applies\n                                to the container.\n                              type: string\n                            type:\n                              description: Type is a SELinux type label that applies\n                                to the container.\n                              type: string\n                            user:\n                              description: User is a SELinux user label that applies\n                                to the container.\n                              type: string\n                          type: object\n                        seccompProfile:\n                          description: The seccomp options to use by this container.\n                            If seccomp options are provided at both the pod & container\n                            level, the container options override the pod options.\n                            Note that this field cannot be set when spec.os.name is\n                            windows.\n                          properties:\n                            localhostProfile:\n                              description: localhostProfile indicates a profile defined\n                                in a file on the node should be used. The profile\n                                must be preconfigured on the node to work. Must be\n                                a descending path, relative to the kubelet's configured\n                                seccomp profile location. Must be set if type is \"Localhost\".\n                                Must NOT be set for any other type.\n                              type: string\n                            type:\n                              description: \"type indicates which kind of seccomp profile\n                                will be applied. Valid options are: \\n Localhost -\n                                a profile defined in a file on the node should be\n                                used. RuntimeDefault - the container runtime default\n                                profile should be used. Unconfined - no profile should\n                                be applied.\"\n                              type: string\n                          required:\n                          - type\n                          type: object\n                        windowsOptions:\n                          description: The Windows specific settings applied to all\n                            containers. If unspecified, the options from the PodSecurityContext\n                            will be used. If set in both SecurityContext and PodSecurityContext,\n                            the value specified in SecurityContext takes precedence.\n                            Note that this field cannot be set when spec.os.name is\n                            linux.\n                          properties:\n                            gmsaCredentialSpec:\n                              description: GMSACredentialSpec is where the GMSA admission\n                                webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                inlines the contents of the GMSA credential spec named\n                                by the GMSACredentialSpecName field.\n                              type: string\n                            gmsaCredentialSpecName:\n                              description: GMSACredentialSpecName is the name of the\n                                GMSA credential spec to use.\n                              type: string\n                            hostProcess:\n                              description: HostProcess determines if a container should\n                                be run as a 'Host Process' container. All of a Pod's\n                                containers must have the same effective HostProcess\n                                value (it is not allowed to have a mix of HostProcess\n                                containers and non-HostProcess containers). In addition,\n                                if HostProcess is true then HostNetwork must also\n                                be set to true.\n                              type: boolean\n                            runAsUserName:\n                              description: The UserName in Windows to run the entrypoint\n                                of the container process. Defaults to the user specified\n                                in image metadata if unspecified. May also be set\n                                in PodSecurityContext. If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence.\n                              type: string\n                          type: object\n                      type: object\n                    startupProbe:\n                      description: 'StartupProbe indicates that the Pod has successfully\n                        initialized. If specified, no other probes are executed until\n                        this completes successfully. If this probe fails, the Pod\n                        will be restarted, just as if the livenessProbe failed. This\n                        can be used to provide different probe parameters at the beginning\n                        of a Pod''s lifecycle, when it might take a long time to load\n                        data or warm a cache, than during steady-state operation.\n                        This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    stdin:\n                      description: Whether this container should allocate a buffer\n                        for stdin in the container runtime. If this is not set, reads\n                        from stdin in the container will always result in EOF. Default\n                        is false.\n                      type: boolean\n                    stdinOnce:\n                      description: Whether the container runtime should close the\n                        stdin channel after it has been opened by a single attach.\n                        When stdin is true the stdin stream will remain open across\n                        multiple attach sessions. If stdinOnce is set to true, stdin\n                        is opened on container start, is empty until the first client\n                        attaches to stdin, and then remains open and accepts data\n                        until the client disconnects, at which time stdin is closed\n                        and remains closed until the container is restarted. If this\n                        flag is false, a container processes that reads from stdin\n                        will never receive an EOF. Default is false\n                      type: boolean\n                    terminationMessagePath:\n                      description: 'Optional: Path at which the file to which the\n                        container''s termination message will be written is mounted\n                        into the container''s filesystem. Message written is intended\n                        to be brief final status, such as an assertion failure message.\n                        Will be truncated by the node if greater than 4096 bytes.\n                        The total message length across all containers will be limited\n                        to 12kb. Defaults to /dev/termination-log. Cannot be updated.'\n                      type: string\n                    terminationMessagePolicy:\n                      description: Indicate how the termination message should be\n                        populated. File will use the contents of terminationMessagePath\n                        to populate the container status message on both success and\n                        failure. FallbackToLogsOnError will use the last chunk of\n                        container log output if the termination message file is empty\n                        and the container exited with an error. The log output is\n                        limited to 2048 bytes or 80 lines, whichever is smaller. Defaults\n                        to File. Cannot be updated.\n                      type: string\n                    tty:\n                      description: Whether this container should allocate a TTY for\n                        itself, also requires 'stdin' to be true. Default is false.\n                      type: boolean\n                    volumeDevices:\n                      description: volumeDevices is the list of block devices to be\n                        used by the container.\n                      items:\n                        description: volumeDevice describes a mapping of a raw block\n                          device within a container.\n                        properties:\n                          devicePath:\n                            description: devicePath is the path inside of the container\n                              that the device will be mapped to.\n                            type: string\n                          name:\n                            description: name must match the name of a persistentVolumeClaim\n                              in the pod\n                            type: string\n                        required:\n                        - devicePath\n                        - name\n                        type: object\n                      type: array\n                    volumeMounts:\n                      description: Pod volumes to mount into the container's filesystem.\n                        Cannot be updated.\n                      items:\n                        description: VolumeMount describes a mounting of a Volume\n                          within a container.\n                        properties:\n                          mountPath:\n                            description: Path within the container at which the volume\n                              should be mounted.  Must not contain ':'.\n                            type: string\n                          mountPropagation:\n                            description: mountPropagation determines how mounts are\n                              propagated from the host to container and the other\n                              way around. When not set, MountPropagationNone is used.\n                              This field is beta in 1.10.\n                            type: string\n                          name:\n                            description: This must match the Name of a Volume.\n                            type: string\n                          readOnly:\n                            description: Mounted read-only if true, read-write otherwise\n                              (false or unspecified). Defaults to false.\n                            type: boolean\n                          subPath:\n                            description: Path within the volume from which the container's\n                              volume should be mounted. Defaults to \"\" (volume's root).\n                            type: string\n                          subPathExpr:\n                            description: Expanded path within the volume from which\n                              the container's volume should be mounted. Behaves similarly\n                              to SubPath but environment variable references $(VAR_NAME)\n                              are expanded using the container's environment. Defaults\n                              to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                              exclusive.\n                            type: string\n                        required:\n                        - mountPath\n                        - name\n                        type: object\n                      type: array\n                    workingDir:\n                      description: Container's working directory. If not specified,\n                        the container runtime's default will be used, which might\n                        be configured in the container image. Cannot be updated.\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              keepDroppedTargets:\n                description: \"Per-scrape limit on the number of targets dropped by\n                  relabeling that will be kept in memory. 0 means no limit. \\n It\n                  requires Prometheus >= v2.47.0.\"\n                format: int64\n                type: integer\n              labelLimit:\n                description: Per-scrape limit on number of labels that will be accepted\n                  for a sample. Only valid in Prometheus versions 2.45.0 and newer.\n                format: int64\n                type: integer\n              labelNameLengthLimit:\n                description: Per-scrape limit on length of labels name that will be\n                  accepted for a sample. Only valid in Prometheus versions 2.45.0\n                  and newer.\n                format: int64\n                type: integer\n              labelValueLengthLimit:\n                description: Per-scrape limit on length of labels value that will\n                  be accepted for a sample. Only valid in Prometheus versions 2.45.0\n                  and newer.\n                format: int64\n                type: integer\n              listenLocal:\n                description: When true, the Prometheus server listens on the loopback\n                  address instead of the Pod IP's address.\n                type: boolean\n              logFormat:\n                description: Log format for Log level for Prometheus and the config-reloader\n                  sidecar.\n                enum:\n                - \"\"\n                - logfmt\n                - json\n                type: string\n              logLevel:\n                description: Log level for Prometheus and the config-reloader sidecar.\n                enum:\n                - \"\"\n                - debug\n                - info\n                - warn\n                - error\n                type: string\n              maximumStartupDurationSeconds:\n                description: Defines the maximum time that the `prometheus` container's\n                  startup probe will wait before being considered failed. The startup\n                  probe will return success after the WAL replay is complete. If set,\n                  the value should be greater than 60 (seconds). Otherwise it will\n                  be equal to 600 seconds (15 minutes).\n                format: int32\n                minimum: 60\n                type: integer\n              minReadySeconds:\n                description: \"Minimum number of seconds for which a newly created\n                  Pod should be ready without any of its container crashing for it\n                  to be considered available. Defaults to 0 (pod will be considered\n                  available as soon as it is ready) \\n This is an alpha field from\n                  kubernetes 1.22 until 1.24 which requires enabling the StatefulSetMinReadySeconds\n                  feature gate.\"\n                format: int32\n                type: integer\n              nodeSelector:\n                additionalProperties:\n                  type: string\n                description: Defines on which Nodes the Pods are scheduled.\n                type: object\n              overrideHonorLabels:\n                description: When true, Prometheus resolves label conflicts by renaming\n                  the labels in the scraped data to \"exported_<label value>\" for all\n                  targets created from service and pod monitors. Otherwise the HonorLabels\n                  field of the service or pod monitor applies.\n                type: boolean\n              overrideHonorTimestamps:\n                description: When true, Prometheus ignores the timestamps for all\n                  the targets created from service and pod monitors. Otherwise the\n                  HonorTimestamps field of the service or pod monitor applies.\n                type: boolean\n              paused:\n                description: When a Prometheus deployment is paused, no actions except\n                  for deletion will be performed on the underlying objects.\n                type: boolean\n              persistentVolumeClaimRetentionPolicy:\n                description: The field controls if and how PVCs are deleted during\n                  the lifecycle of a StatefulSet. The default behavior is all PVCs\n                  are retained. This is an alpha field from kubernetes 1.23 until\n                  1.26 and a beta field from 1.26. It requires enabling the StatefulSetAutoDeletePVC\n                  feature gate.\n                properties:\n                  whenDeleted:\n                    description: WhenDeleted specifies what happens to PVCs created\n                      from StatefulSet VolumeClaimTemplates when the StatefulSet is\n                      deleted. The default policy of `Retain` causes PVCs to not be\n                      affected by StatefulSet deletion. The `Delete` policy causes\n                      those PVCs to be deleted.\n                    type: string\n                  whenScaled:\n                    description: WhenScaled specifies what happens to PVCs created\n                      from StatefulSet VolumeClaimTemplates when the StatefulSet is\n                      scaled down. The default policy of `Retain` causes PVCs to not\n                      be affected by a scaledown. The `Delete` policy causes the associated\n                      PVCs for any excess pods above the replica count to be deleted.\n                    type: string\n                type: object\n              podMetadata:\n                description: \"PodMetadata configures labels and annotations which\n                  are propagated to the Prometheus pods. \\n The following items are\n                  reserved and cannot be overridden: * \\\"prometheus\\\" label, set to\n                  the name of the Prometheus object. * \\\"app.kubernetes.io/instance\\\"\n                  label, set to the name of the Prometheus object. * \\\"app.kubernetes.io/managed-by\\\"\n                  label, set to \\\"prometheus-operator\\\". * \\\"app.kubernetes.io/name\\\"\n                  label, set to \\\"prometheus\\\". * \\\"app.kubernetes.io/version\\\" label,\n                  set to the Prometheus version. * \\\"operator.prometheus.io/name\\\"\n                  label, set to the name of the Prometheus object. * \\\"operator.prometheus.io/shard\\\"\n                  label, set to the shard number of the Prometheus object. * \\\"kubectl.kubernetes.io/default-container\\\"\n                  annotation, set to \\\"prometheus\\\".\"\n                properties:\n                  annotations:\n                    additionalProperties:\n                      type: string\n                    description: 'Annotations is an unstructured key value map stored\n                      with a resource that may be set by external tools to store and\n                      retrieve arbitrary metadata. They are not queryable and should\n                      be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'\n                    type: object\n                  labels:\n                    additionalProperties:\n                      type: string\n                    description: 'Map of string keys and values that can be used to\n                      organize and categorize (scope and select) objects. May match\n                      selectors of replication controllers and services. More info:\n                      http://kubernetes.io/docs/user-guide/labels'\n                    type: object\n                  name:\n                    description: 'Name must be unique within a namespace. Is required\n                      when creating resources, although some resources may allow a\n                      client to request the generation of an appropriate name automatically.\n                      Name is primarily intended for creation idempotence and configuration\n                      definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'\n                    type: string\n                type: object\n              podMonitorNamespaceSelector:\n                description: Namespaces to match for PodMonitors discovery. An empty\n                  label selector matches all namespaces. A null label selector matches\n                  the current namespace only.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              podMonitorSelector:\n                description: \"*Experimental* PodMonitors to be selected for target\n                  discovery. An empty label selector matches all objects. A null label\n                  selector matches no objects. \\n If `spec.serviceMonitorSelector`,\n                  `spec.podMonitorSelector`, `spec.probeSelector` and `spec.scrapeConfigSelector`\n                  are null, the Prometheus configuration is unmanaged. The Prometheus\n                  operator will ensure that the Prometheus configuration's Secret\n                  exists, but it is the responsibility of the user to provide the\n                  raw gzipped Prometheus configuration under the `prometheus.yaml.gz`\n                  key. This behavior is *deprecated* and will be removed in the next\n                  major version of the custom resource definition. It is recommended\n                  to use `spec.additionalScrapeConfigs` instead.\"\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              podTargetLabels:\n                description: PodTargetLabels are appended to the `spec.podTargetLabels`\n                  field of all PodMonitor and ServiceMonitor objects.\n                items:\n                  type: string\n                type: array\n              portName:\n                default: web\n                description: 'Port name used for the pods and governing service. Default:\n                  \"web\"'\n                type: string\n              priorityClassName:\n                description: Priority class assigned to the Pods.\n                type: string\n              probeNamespaceSelector:\n                description: '*Experimental* Namespaces to match for Probe discovery.\n                  An empty label selector matches all namespaces. A null label selector\n                  matches the current namespace only.'\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              probeSelector:\n                description: \"*Experimental* Probes to be selected for target discovery.\n                  An empty label selector matches all objects. A null label selector\n                  matches no objects. \\n If `spec.serviceMonitorSelector`, `spec.podMonitorSelector`,\n                  `spec.probeSelector` and `spec.scrapeConfigSelector` are null, the\n                  Prometheus configuration is unmanaged. The Prometheus operator will\n                  ensure that the Prometheus configuration's Secret exists, but it\n                  is the responsibility of the user to provide the raw gzipped Prometheus\n                  configuration under the `prometheus.yaml.gz` key. This behavior\n                  is *deprecated* and will be removed in the next major version of\n                  the custom resource definition. It is recommended to use `spec.additionalScrapeConfigs`\n                  instead.\"\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              prometheusExternalLabelName:\n                description: \"Name of Prometheus external label used to denote the\n                  Prometheus instance name. The external label will _not_ be added\n                  when the field is set to the empty string (`\\\"\\\"`). \\n Default:\n                  \\\"prometheus\\\"\"\n                type: string\n              prometheusRulesExcludedFromEnforce:\n                description: 'Defines the list of PrometheusRule objects to which\n                  the namespace label enforcement doesn''t apply. This is only relevant\n                  when `spec.enforcedNamespaceLabel` is set to true. Deprecated: use\n                  `spec.excludedFromEnforcement` instead.'\n                items:\n                  description: PrometheusRuleExcludeConfig enables users to configure\n                    excluded PrometheusRule names and their namespaces to be ignored\n                    while enforcing namespace label for alerts and metrics.\n                  properties:\n                    ruleName:\n                      description: Name of the excluded PrometheusRule object.\n                      type: string\n                    ruleNamespace:\n                      description: Namespace of the excluded PrometheusRule object.\n                      type: string\n                  required:\n                  - ruleName\n                  - ruleNamespace\n                  type: object\n                type: array\n              query:\n                description: QuerySpec defines the configuration of the Promethus\n                  query service.\n                properties:\n                  lookbackDelta:\n                    description: The delta difference allowed for retrieving metrics\n                      during expression evaluations.\n                    type: string\n                  maxConcurrency:\n                    description: Number of concurrent queries that can be run at once.\n                    format: int32\n                    minimum: 1\n                    type: integer\n                  maxSamples:\n                    description: Maximum number of samples a single query can load\n                      into memory. Note that queries will fail if they would load\n                      more samples than this into memory, so this also limits the\n                      number of samples a query can return.\n                    format: int32\n                    type: integer\n                  timeout:\n                    description: Maximum time a query may take before being aborted.\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                type: object\n              queryLogFile:\n                description: \"queryLogFile specifies where the file to which PromQL\n                  queries are logged. \\n If the filename has an empty path, e.g. 'query.log',\n                  The Prometheus Pods will mount the file into an emptyDir volume\n                  at `/var/log/prometheus`. If a full path is provided, e.g. '/var/log/prometheus/query.log',\n                  you must mount a volume in the specified directory and it must be\n                  writable. This is because the prometheus container runs with a read-only\n                  root filesystem for security reasons. Alternatively, the location\n                  can be set to a standard I/O stream, e.g. `/dev/stdout`, to log\n                  query information to the default Prometheus log stream.\"\n                type: string\n              reloadStrategy:\n                description: Defines the strategy used to reload the Prometheus configuration.\n                  If not specified, the configuration is reloaded using the /-/reload\n                  HTTP endpoint.\n                enum:\n                - HTTP\n                - ProcessSignal\n                type: string\n              remoteRead:\n                description: Defines the list of remote read configurations.\n                items:\n                  description: RemoteReadSpec defines the configuration for Prometheus\n                    to read back samples from a remote endpoint.\n                  properties:\n                    authorization:\n                      description: \"Authorization section for the URL. \\n It requires\n                        Prometheus >= v2.26.0. \\n Cannot be set at the same time as\n                        `basicAuth`, or `oauth2`.\"\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        credentialsFile:\n                          description: File to read a secret from, mutually exclusive\n                            with `credentials`.\n                          type: string\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    basicAuth:\n                      description: \"BasicAuth configuration for the URL. \\n Cannot\n                        be set at the same time as `authorization`, or `oauth2`.\"\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    bearerToken:\n                      description: \"*Warning: this field shouldn't be used because\n                        the token value appears in clear-text. Prefer using `authorization`.*\n                        \\n Deprecated: this will be removed in a future release.\"\n                      type: string\n                    bearerTokenFile:\n                      description: \"File from which to read the bearer token for the\n                        URL. \\n Deprecated: this will be removed in a future release.\n                        Prefer using `authorization`.\"\n                      type: string\n                    filterExternalLabels:\n                      description: \"Whether to use the external labels as selectors\n                        for the remote read endpoint. \\n It requires Prometheus >=\n                        v2.34.0.\"\n                      type: boolean\n                    followRedirects:\n                      description: \"Configure whether HTTP requests follow HTTP 3xx\n                        redirects. \\n It requires Prometheus >= v2.26.0.\"\n                      type: boolean\n                    headers:\n                      additionalProperties:\n                        type: string\n                      description: Custom HTTP headers to be sent along with each\n                        remote read request. Be aware that headers that are set by\n                        Prometheus itself can't be overwritten. Only valid in Prometheus\n                        versions 2.26.0 and newer.\n                      type: object\n                    name:\n                      description: \"The name of the remote read queue, it must be\n                        unique if specified. The name is used in metrics and logging\n                        in order to differentiate read configurations. \\n It requires\n                        Prometheus >= v2.15.0.\"\n                      type: string\n                    oauth2:\n                      description: \"OAuth2 configuration for the URL. \\n It requires\n                        Prometheus >= v2.27.0. \\n Cannot be set at the same time as\n                        `authorization`, or `basicAuth`.\"\n                      properties:\n                        clientId:\n                          description: '`clientId` specifies a key of a Secret or\n                            ConfigMap containing the OAuth2 client''s ID.'\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        clientSecret:\n                          description: '`clientSecret` specifies a key of a Secret\n                            containing the OAuth2 client''s secret.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        endpointParams:\n                          additionalProperties:\n                            type: string\n                          description: '`endpointParams` configures the HTTP parameters\n                            to append to the token URL.'\n                          type: object\n                        scopes:\n                          description: '`scopes` defines the OAuth2 scopes used for\n                            the token request.'\n                          items:\n                            type: string\n                          type: array\n                        tokenUrl:\n                          description: '`tokenURL` configures the URL to fetch the\n                            token from.'\n                          minLength: 1\n                          type: string\n                      required:\n                      - clientId\n                      - clientSecret\n                      - tokenUrl\n                      type: object\n                    proxyUrl:\n                      description: Optional ProxyURL.\n                      type: string\n                    readRecent:\n                      description: Whether reads should be made for queries for time\n                        ranges that the local storage should have complete data for.\n                      type: boolean\n                    remoteTimeout:\n                      description: Timeout for requests to the remote read endpoint.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    requiredMatchers:\n                      additionalProperties:\n                        type: string\n                      description: An optional list of equality matchers which have\n                        to be present in a selector to query the remote read endpoint.\n                      type: object\n                    tlsConfig:\n                      description: TLS Config to use for the URL.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        caFile:\n                          description: Path to the CA cert in the Prometheus container\n                            to use for the targets.\n                          type: string\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        certFile:\n                          description: Path to the client cert file in the Prometheus\n                            container for the targets.\n                          type: string\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keyFile:\n                          description: Path to the client key file in the Prometheus\n                            container for the targets.\n                          type: string\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    url:\n                      description: The URL of the endpoint to query from.\n                      type: string\n                  required:\n                  - url\n                  type: object\n                type: array\n              remoteWrite:\n                description: Defines the list of remote write configurations.\n                items:\n                  description: RemoteWriteSpec defines the configuration to write\n                    samples from Prometheus to a remote endpoint.\n                  properties:\n                    authorization:\n                      description: \"Authorization section for the URL. \\n It requires\n                        Prometheus >= v2.26.0. \\n Cannot be set at the same time as\n                        `sigv4`, `basicAuth`, `oauth2`, or `azureAd`.\"\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        credentialsFile:\n                          description: File to read a secret from, mutually exclusive\n                            with `credentials`.\n                          type: string\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    azureAd:\n                      description: \"AzureAD for the URL. \\n It requires Prometheus\n                        >= v2.45.0. \\n Cannot be set at the same time as `authorization`,\n                        `basicAuth`, `oauth2`, or `sigv4`.\"\n                      properties:\n                        cloud:\n                          description: The Azure Cloud. Options are 'AzurePublic',\n                            'AzureChina', or 'AzureGovernment'.\n                          enum:\n                          - AzureChina\n                          - AzureGovernment\n                          - AzurePublic\n                          type: string\n                        managedIdentity:\n                          description: ManagedIdentity defines the Azure User-assigned\n                            Managed identity. Cannot be set at the same time as `oauth`.\n                          properties:\n                            clientId:\n                              description: The client id\n                              type: string\n                          required:\n                          - clientId\n                          type: object\n                        oauth:\n                          description: \"OAuth defines the oauth config that is being\n                            used to authenticate. Cannot be set at the same time as\n                            `managedIdentity`. \\n It requires Prometheus >= v2.48.0.\"\n                          properties:\n                            clientId:\n                              description: '`clientID` is the clientId of the Azure\n                                Active Directory application that is being used to\n                                authenticate.'\n                              minLength: 1\n                              type: string\n                            clientSecret:\n                              description: '`clientSecret` specifies a key of a Secret\n                                containing the client secret of the Azure Active Directory\n                                application that is being used to authenticate.'\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            tenantId:\n                              description: '`tenantID` is the tenant ID of the Azure\n                                Active Directory application that is being used to\n                                authenticate.'\n                              minLength: 1\n                              pattern: ^[0-9a-zA-Z-.]+$\n                              type: string\n                          required:\n                          - clientId\n                          - clientSecret\n                          - tenantId\n                          type: object\n                      type: object\n                    basicAuth:\n                      description: \"BasicAuth configuration for the URL. \\n Cannot\n                        be set at the same time as `sigv4`, `authorization`, `oauth2`,\n                        or `azureAd`.\"\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    bearerToken:\n                      description: \"*Warning: this field shouldn't be used because\n                        the token value appears in clear-text. Prefer using `authorization`.*\n                        \\n Deprecated: this will be removed in a future release.\"\n                      type: string\n                    bearerTokenFile:\n                      description: \"File from which to read bearer token for the URL.\n                        \\n Deprecated: this will be removed in a future release. Prefer\n                        using `authorization`.\"\n                      type: string\n                    enableHTTP2:\n                      description: Whether to enable HTTP2.\n                      type: boolean\n                    headers:\n                      additionalProperties:\n                        type: string\n                      description: \"Custom HTTP headers to be sent along with each\n                        remote write request. Be aware that headers that are set by\n                        Prometheus itself can't be overwritten. \\n It requires Prometheus\n                        >= v2.25.0.\"\n                      type: object\n                    metadataConfig:\n                      description: MetadataConfig configures the sending of series\n                        metadata to the remote storage.\n                      properties:\n                        send:\n                          description: Defines whether metric metadata is sent to\n                            the remote storage or not.\n                          type: boolean\n                        sendInterval:\n                          description: Defines how frequently metric metadata is sent\n                            to the remote storage.\n                          pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                          type: string\n                      type: object\n                    name:\n                      description: \"The name of the remote write queue, it must be\n                        unique if specified. The name is used in metrics and logging\n                        in order to differentiate queues. \\n It requires Prometheus\n                        >= v2.15.0.\"\n                      type: string\n                    oauth2:\n                      description: \"OAuth2 configuration for the URL. \\n It requires\n                        Prometheus >= v2.27.0. \\n Cannot be set at the same time as\n                        `sigv4`, `authorization`, `basicAuth`, or `azureAd`.\"\n                      properties:\n                        clientId:\n                          description: '`clientId` specifies a key of a Secret or\n                            ConfigMap containing the OAuth2 client''s ID.'\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        clientSecret:\n                          description: '`clientSecret` specifies a key of a Secret\n                            containing the OAuth2 client''s secret.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        endpointParams:\n                          additionalProperties:\n                            type: string\n                          description: '`endpointParams` configures the HTTP parameters\n                            to append to the token URL.'\n                          type: object\n                        scopes:\n                          description: '`scopes` defines the OAuth2 scopes used for\n                            the token request.'\n                          items:\n                            type: string\n                          type: array\n                        tokenUrl:\n                          description: '`tokenURL` configures the URL to fetch the\n                            token from.'\n                          minLength: 1\n                          type: string\n                      required:\n                      - clientId\n                      - clientSecret\n                      - tokenUrl\n                      type: object\n                    proxyUrl:\n                      description: Optional ProxyURL.\n                      type: string\n                    queueConfig:\n                      description: QueueConfig allows tuning of the remote write queue\n                        parameters.\n                      properties:\n                        batchSendDeadline:\n                          description: BatchSendDeadline is the maximum time a sample\n                            will wait in buffer.\n                          type: string\n                        capacity:\n                          description: Capacity is the number of samples to buffer\n                            per shard before we start dropping them.\n                          type: integer\n                        maxBackoff:\n                          description: MaxBackoff is the maximum retry delay.\n                          type: string\n                        maxRetries:\n                          description: MaxRetries is the maximum number of times to\n                            retry a batch on recoverable errors.\n                          type: integer\n                        maxSamplesPerSend:\n                          description: MaxSamplesPerSend is the maximum number of\n                            samples per send.\n                          type: integer\n                        maxShards:\n                          description: MaxShards is the maximum number of shards,\n                            i.e. amount of concurrency.\n                          type: integer\n                        minBackoff:\n                          description: MinBackoff is the initial retry delay. Gets\n                            doubled for every retry.\n                          type: string\n                        minShards:\n                          description: MinShards is the minimum number of shards,\n                            i.e. amount of concurrency.\n                          type: integer\n                        retryOnRateLimit:\n                          description: Retry upon receiving a 429 status code from\n                            the remote-write storage. This is experimental feature\n                            and might change in the future.\n                          type: boolean\n                      type: object\n                    remoteTimeout:\n                      description: Timeout for requests to the remote write endpoint.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    sendExemplars:\n                      description: \"Enables sending of exemplars over remote write.\n                        Note that exemplar-storage itself must be enabled using the\n                        `spec.enableFeature` option for exemplars to be scraped in\n                        the first place. \\n It requires Prometheus >= v2.27.0.\"\n                      type: boolean\n                    sendNativeHistograms:\n                      description: \"Enables sending of native histograms, also known\n                        as sparse histograms over remote write. \\n It requires Prometheus\n                        >= v2.40.0.\"\n                      type: boolean\n                    sigv4:\n                      description: \"Sigv4 allows to configures AWS's Signature Verification\n                        4 for the URL. \\n It requires Prometheus >= v2.26.0. \\n Cannot\n                        be set at the same time as `authorization`, `basicAuth`, `oauth2`,\n                        or `azureAd`.\"\n                      properties:\n                        accessKey:\n                          description: AccessKey is the AWS API key. If not specified,\n                            the environment variable `AWS_ACCESS_KEY_ID` is used.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        profile:\n                          description: Profile is the named AWS profile used to authenticate.\n                          type: string\n                        region:\n                          description: Region is the AWS region. If blank, the region\n                            from the default credentials chain used.\n                          type: string\n                        roleArn:\n                          description: RoleArn is the named AWS profile used to authenticate.\n                          type: string\n                        secretKey:\n                          description: SecretKey is the AWS API secret. If not specified,\n                            the environment variable `AWS_SECRET_ACCESS_KEY` is used.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    tlsConfig:\n                      description: TLS Config to use for the URL.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        caFile:\n                          description: Path to the CA cert in the Prometheus container\n                            to use for the targets.\n                          type: string\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        certFile:\n                          description: Path to the client cert file in the Prometheus\n                            container for the targets.\n                          type: string\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keyFile:\n                          description: Path to the client key file in the Prometheus\n                            container for the targets.\n                          type: string\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    url:\n                      description: The URL of the endpoint to send samples to.\n                      type: string\n                    writeRelabelConfigs:\n                      description: The list of remote write relabel configurations.\n                      items:\n                        description: \"RelabelConfig allows dynamic rewriting of the\n                          label set for targets, alerts, scraped samples and remote\n                          write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                        properties:\n                          action:\n                            default: replace\n                            description: \"Action to perform based on the regex matching.\n                              \\n `Uppercase` and `Lowercase` actions require Prometheus\n                              >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                              Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                            enum:\n                            - replace\n                            - Replace\n                            - keep\n                            - Keep\n                            - drop\n                            - Drop\n                            - hashmod\n                            - HashMod\n                            - labelmap\n                            - LabelMap\n                            - labeldrop\n                            - LabelDrop\n                            - labelkeep\n                            - LabelKeep\n                            - lowercase\n                            - Lowercase\n                            - uppercase\n                            - Uppercase\n                            - keepequal\n                            - KeepEqual\n                            - dropequal\n                            - DropEqual\n                            type: string\n                          modulus:\n                            description: \"Modulus to take of the hash of the source\n                              label values. \\n Only applicable when the action is\n                              `HashMod`.\"\n                            format: int64\n                            type: integer\n                          regex:\n                            description: Regular expression against which the extracted\n                              value is matched.\n                            type: string\n                          replacement:\n                            description: \"Replacement value against which a Replace\n                              action is performed if the regular expression matches.\n                              \\n Regex capture groups are available.\"\n                            type: string\n                          separator:\n                            description: Separator is the string between concatenated\n                              SourceLabels.\n                            type: string\n                          sourceLabels:\n                            description: The source labels select values from existing\n                              labels. Their content is concatenated using the configured\n                              Separator and matched against the configured regular\n                              expression.\n                            items:\n                              description: LabelName is a valid Prometheus label name\n                                which may only contain ASCII letters, numbers, as\n                                well as underscores.\n                              pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                              type: string\n                            type: array\n                          targetLabel:\n                            description: \"Label to which the resulting string is written\n                              in a replacement. \\n It is mandatory for `Replace`,\n                              `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                              `DropEqual` actions. \\n Regex capture groups are available.\"\n                            type: string\n                        type: object\n                      type: array\n                  required:\n                  - url\n                  type: object\n                type: array\n              replicaExternalLabelName:\n                description: \"Name of Prometheus external label used to denote the\n                  replica name. The external label will _not_ be added when the field\n                  is set to the empty string (`\\\"\\\"`). \\n Default: \\\"prometheus_replica\\\"\"\n                type: string\n              replicas:\n                description: \"Number of replicas of each shard to deploy for a Prometheus\n                  deployment. `spec.replicas` multiplied by `spec.shards` is the total\n                  number of Pods created. \\n Default: 1\"\n                format: int32\n                type: integer\n              resources:\n                description: Defines the resources requests and limits of the 'prometheus'\n                  container.\n                properties:\n                  claims:\n                    description: \"Claims lists the names of resources, defined in\n                      spec.resourceClaims, that are used by this container. \\n This\n                      is an alpha field and requires enabling the DynamicResourceAllocation\n                      feature gate. \\n This field is immutable. It can only be set\n                      for containers.\"\n                    items:\n                      description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                      properties:\n                        name:\n                          description: Name must match the name of one entry in pod.spec.resourceClaims\n                            of the Pod where this field is used. It makes that resource\n                            available inside a container.\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    type: array\n                    x-kubernetes-list-map-keys:\n                    - name\n                    x-kubernetes-list-type: map\n                  limits:\n                    additionalProperties:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                      x-kubernetes-int-or-string: true\n                    description: 'Limits describes the maximum amount of compute resources\n                      allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                    type: object\n                  requests:\n                    additionalProperties:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                      x-kubernetes-int-or-string: true\n                    description: 'Requests describes the minimum amount of compute\n                      resources required. If Requests is omitted for a container,\n                      it defaults to Limits if that is explicitly specified, otherwise\n                      to an implementation-defined value. Requests cannot exceed Limits.\n                      More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                    type: object\n                type: object\n              retention:\n                description: \"How long to retain the Prometheus data. \\n Default:\n                  \\\"24h\\\" if `spec.retention` and `spec.retentionSize` are empty.\"\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              retentionSize:\n                description: Maximum number of bytes used by the Prometheus data.\n                pattern: (^0|([0-9]*[.])?[0-9]+((K|M|G|T|E|P)i?)?B)$\n                type: string\n              routePrefix:\n                description: \"The route prefix Prometheus registers HTTP handlers\n                  for. \\n This is useful when using `spec.externalURL`, and a proxy\n                  is rewriting HTTP routes of a request, and the actual ExternalURL\n                  is still true, but the server serves requests under a different\n                  route prefix. For example for use with `kubectl proxy`.\"\n                type: string\n              ruleNamespaceSelector:\n                description: Namespaces to match for PrometheusRule discovery. An\n                  empty label selector matches all namespaces. A null label selector\n                  matches the current namespace only.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              ruleSelector:\n                description: PrometheusRule objects to be selected for rule evaluation.\n                  An empty label selector matches all objects. A null label selector\n                  matches no objects.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              rules:\n                description: Defines the configuration of the Prometheus rules' engine.\n                properties:\n                  alert:\n                    description: \"Defines the parameters of the Prometheus rules'\n                      engine. \\n Any update to these parameters trigger a restart\n                      of the pods.\"\n                    properties:\n                      forGracePeriod:\n                        description: \"Minimum duration between alert and restored\n                          'for' state. \\n This is maintained only for alerts with\n                          a configured 'for' time greater than the grace period.\"\n                        type: string\n                      forOutageTolerance:\n                        description: Max time to tolerate prometheus outage for restoring\n                          'for' state of alert.\n                        type: string\n                      resendDelay:\n                        description: Minimum amount of time to wait before resending\n                          an alert to Alertmanager.\n                        type: string\n                    type: object\n                type: object\n              sampleLimit:\n                description: SampleLimit defines per-scrape limit on number of scraped\n                  samples that will be accepted. Only valid in Prometheus versions\n                  2.45.0 and newer.\n                format: int64\n                type: integer\n              scrapeConfigNamespaceSelector:\n                description: Namespaces to match for ScrapeConfig discovery. An empty\n                  label selector matches all namespaces. A null label selector matches\n                  the current current namespace only.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              scrapeConfigSelector:\n                description: \"*Experimental* ScrapeConfigs to be selected for target\n                  discovery. An empty label selector matches all objects. A null label\n                  selector matches no objects. \\n If `spec.serviceMonitorSelector`,\n                  `spec.podMonitorSelector`, `spec.probeSelector` and `spec.scrapeConfigSelector`\n                  are null, the Prometheus configuration is unmanaged. The Prometheus\n                  operator will ensure that the Prometheus configuration's Secret\n                  exists, but it is the responsibility of the user to provide the\n                  raw gzipped Prometheus configuration under the `prometheus.yaml.gz`\n                  key. This behavior is *deprecated* and will be removed in the next\n                  major version of the custom resource definition. It is recommended\n                  to use `spec.additionalScrapeConfigs` instead.\"\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              scrapeInterval:\n                default: 30s\n                description: \"Interval between consecutive scrapes. \\n Default: \\\"30s\\\"\"\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              scrapeTimeout:\n                description: Number of seconds to wait until a scrape request times\n                  out.\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              secrets:\n                description: Secrets is a list of Secrets in the same namespace as\n                  the Prometheus object, which shall be mounted into the Prometheus\n                  Pods. Each Secret is added to the StatefulSet definition as a volume\n                  named `secret-<secret-name>`. The Secrets are mounted into /etc/prometheus/secrets/<secret-name>\n                  in the 'prometheus' container.\n                items:\n                  type: string\n                type: array\n              securityContext:\n                description: SecurityContext holds pod-level security attributes and\n                  common container settings. This defaults to the default PodSecurityContext.\n                properties:\n                  fsGroup:\n                    description: \"A special supplemental group that applies to all\n                      containers in a pod. Some volume types allow the Kubelet to\n                      change the ownership of that volume to be owned by the pod:\n                      \\n 1. The owning GID will be the FSGroup 2. The setgid bit is\n                      set (new files created in the volume will be owned by FSGroup)\n                      3. The permission bits are OR'd with rw-rw---- \\n If unset,\n                      the Kubelet will not modify the ownership and permissions of\n                      any volume. Note that this field cannot be set when spec.os.name\n                      is windows.\"\n                    format: int64\n                    type: integer\n                  fsGroupChangePolicy:\n                    description: 'fsGroupChangePolicy defines behavior of changing\n                      ownership and permission of the volume before being exposed\n                      inside Pod. This field will only apply to volume types which\n                      support fsGroup based ownership(and permissions). It will have\n                      no effect on ephemeral volume types such as: secret, configmaps\n                      and emptydir. Valid values are \"OnRootMismatch\" and \"Always\".\n                      If not specified, \"Always\" is used. Note that this field cannot\n                      be set when spec.os.name is windows.'\n                    type: string\n                  runAsGroup:\n                    description: The GID to run the entrypoint of the container process.\n                      Uses runtime default if unset. May also be set in SecurityContext.  If\n                      set in both SecurityContext and PodSecurityContext, the value\n                      specified in SecurityContext takes precedence for that container.\n                      Note that this field cannot be set when spec.os.name is windows.\n                    format: int64\n                    type: integer\n                  runAsNonRoot:\n                    description: Indicates that the container must run as a non-root\n                      user. If true, the Kubelet will validate the image at runtime\n                      to ensure that it does not run as UID 0 (root) and fail to start\n                      the container if it does. If unset or false, no such validation\n                      will be performed. May also be set in SecurityContext.  If set\n                      in both SecurityContext and PodSecurityContext, the value specified\n                      in SecurityContext takes precedence.\n                    type: boolean\n                  runAsUser:\n                    description: The UID to run the entrypoint of the container process.\n                      Defaults to user specified in image metadata if unspecified.\n                      May also be set in SecurityContext.  If set in both SecurityContext\n                      and PodSecurityContext, the value specified in SecurityContext\n                      takes precedence for that container. Note that this field cannot\n                      be set when spec.os.name is windows.\n                    format: int64\n                    type: integer\n                  seLinuxOptions:\n                    description: The SELinux context to be applied to all containers.\n                      If unspecified, the container runtime will allocate a random\n                      SELinux context for each container.  May also be set in SecurityContext.  If\n                      set in both SecurityContext and PodSecurityContext, the value\n                      specified in SecurityContext takes precedence for that container.\n                      Note that this field cannot be set when spec.os.name is windows.\n                    properties:\n                      level:\n                        description: Level is SELinux level label that applies to\n                          the container.\n                        type: string\n                      role:\n                        description: Role is a SELinux role label that applies to\n                          the container.\n                        type: string\n                      type:\n                        description: Type is a SELinux type label that applies to\n                          the container.\n                        type: string\n                      user:\n                        description: User is a SELinux user label that applies to\n                          the container.\n                        type: string\n                    type: object\n                  seccompProfile:\n                    description: The seccomp options to use by the containers in this\n                      pod. Note that this field cannot be set when spec.os.name is\n                      windows.\n                    properties:\n                      localhostProfile:\n                        description: localhostProfile indicates a profile defined\n                          in a file on the node should be used. The profile must be\n                          preconfigured on the node to work. Must be a descending\n                          path, relative to the kubelet's configured seccomp profile\n                          location. Must be set if type is \"Localhost\". Must NOT be\n                          set for any other type.\n                        type: string\n                      type:\n                        description: \"type indicates which kind of seccomp profile\n                          will be applied. Valid options are: \\n Localhost - a profile\n                          defined in a file on the node should be used. RuntimeDefault\n                          - the container runtime default profile should be used.\n                          Unconfined - no profile should be applied.\"\n                        type: string\n                    required:\n                    - type\n                    type: object\n                  supplementalGroups:\n                    description: A list of groups applied to the first process run\n                      in each container, in addition to the container's primary GID,\n                      the fsGroup (if specified), and group memberships defined in\n                      the container image for the uid of the container process. If\n                      unspecified, no additional groups are added to any container.\n                      Note that group memberships defined in the container image for\n                      the uid of the container process are still effective, even if\n                      they are not included in this list. Note that this field cannot\n                      be set when spec.os.name is windows.\n                    items:\n                      format: int64\n                      type: integer\n                    type: array\n                  sysctls:\n                    description: Sysctls hold a list of namespaced sysctls used for\n                      the pod. Pods with unsupported sysctls (by the container runtime)\n                      might fail to launch. Note that this field cannot be set when\n                      spec.os.name is windows.\n                    items:\n                      description: Sysctl defines a kernel parameter to be set\n                      properties:\n                        name:\n                          description: Name of a property to set\n                          type: string\n                        value:\n                          description: Value of a property to set\n                          type: string\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  windowsOptions:\n                    description: The Windows specific settings applied to all containers.\n                      If unspecified, the options within a container's SecurityContext\n                      will be used. If set in both SecurityContext and PodSecurityContext,\n                      the value specified in SecurityContext takes precedence. Note\n                      that this field cannot be set when spec.os.name is linux.\n                    properties:\n                      gmsaCredentialSpec:\n                        description: GMSACredentialSpec is where the GMSA admission\n                          webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                          inlines the contents of the GMSA credential spec named by\n                          the GMSACredentialSpecName field.\n                        type: string\n                      gmsaCredentialSpecName:\n                        description: GMSACredentialSpecName is the name of the GMSA\n                          credential spec to use.\n                        type: string\n                      hostProcess:\n                        description: HostProcess determines if a container should\n                          be run as a 'Host Process' container. All of a Pod's containers\n                          must have the same effective HostProcess value (it is not\n                          allowed to have a mix of HostProcess containers and non-HostProcess\n                          containers). In addition, if HostProcess is true then HostNetwork\n                          must also be set to true.\n                        type: boolean\n                      runAsUserName:\n                        description: The UserName in Windows to run the entrypoint\n                          of the container process. Defaults to the user specified\n                          in image metadata if unspecified. May also be set in PodSecurityContext.\n                          If set in both SecurityContext and PodSecurityContext, the\n                          value specified in SecurityContext takes precedence.\n                        type: string\n                    type: object\n                type: object\n              serviceAccountName:\n                description: ServiceAccountName is the name of the ServiceAccount\n                  to use to run the Prometheus Pods.\n                type: string\n              serviceMonitorNamespaceSelector:\n                description: Namespaces to match for ServicedMonitors discovery. An\n                  empty label selector matches all namespaces. A null label selector\n                  matches the current namespace only.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              serviceMonitorSelector:\n                description: \"ServiceMonitors to be selected for target discovery.\n                  An empty label selector matches all objects. A null label selector\n                  matches no objects. \\n If `spec.serviceMonitorSelector`, `spec.podMonitorSelector`,\n                  `spec.probeSelector` and `spec.scrapeConfigSelector` are null, the\n                  Prometheus configuration is unmanaged. The Prometheus operator will\n                  ensure that the Prometheus configuration's Secret exists, but it\n                  is the responsibility of the user to provide the raw gzipped Prometheus\n                  configuration under the `prometheus.yaml.gz` key. This behavior\n                  is *deprecated* and will be removed in the next major version of\n                  the custom resource definition. It is recommended to use `spec.additionalScrapeConfigs`\n                  instead.\"\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              sha:\n                description: 'Deprecated: use ''spec.image'' instead. The image''s\n                  digest can be specified as part of the image name.'\n                type: string\n              shards:\n                description: \"EXPERIMENTAL: Number of shards to distribute targets\n                  onto. `spec.replicas` multiplied by `spec.shards` is the total number\n                  of Pods created. \\n Note that scaling down shards will not reshard\n                  data onto remaining instances, it must be manually moved. Increasing\n                  shards will not reshard data either but it will continue to be available\n                  from the same instances. To query globally, use Thanos sidecar and\n                  Thanos querier or remote write data to a central location. \\n Sharding\n                  is performed on the content of the `__address__` target meta-label\n                  for PodMonitors and ServiceMonitors and `__param_target__` for Probes.\n                  \\n Default: 1\"\n                format: int32\n                type: integer\n              storage:\n                description: Storage defines the storage used by Prometheus.\n                properties:\n                  disableMountSubPath:\n                    description: 'Deprecated: subPath usage will be removed in a future\n                      release.'\n                    type: boolean\n                  emptyDir:\n                    description: 'EmptyDirVolumeSource to be used by the StatefulSet.\n                      If specified, it takes precedence over `ephemeral` and `volumeClaimTemplate`.\n                      More info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir'\n                    properties:\n                      medium:\n                        description: 'medium represents what type of storage medium\n                          should back this directory. The default is \"\" which means\n                          to use the node''s default medium. Must be an empty string\n                          (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                        type: string\n                      sizeLimit:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: 'sizeLimit is the total amount of local storage\n                          required for this EmptyDir volume. The size limit is also\n                          applicable for memory medium. The maximum usage on memory\n                          medium EmptyDir would be the minimum value between the SizeLimit\n                          specified here and the sum of memory limits of all containers\n                          in a pod. The default is nil which means that the limit\n                          is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                    type: object\n                  ephemeral:\n                    description: 'EphemeralVolumeSource to be used by the StatefulSet.\n                      This is a beta field in k8s 1.21 and GA in 1.15. For lower versions,\n                      starting with k8s 1.19, it requires enabling the GenericEphemeralVolume\n                      feature gate. More info: https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes'\n                    properties:\n                      volumeClaimTemplate:\n                        description: \"Will be used to create a stand-alone PVC to\n                          provision the volume. The pod in which this EphemeralVolumeSource\n                          is embedded will be the owner of the PVC, i.e. the PVC will\n                          be deleted together with the pod.  The name of the PVC will\n                          be `<pod name>-<volume name>` where `<volume name>` is the\n                          name from the `PodSpec.Volumes` array entry. Pod validation\n                          will reject the pod if the concatenated name is not valid\n                          for a PVC (for example, too long). \\n An existing PVC with\n                          that name that is not owned by the pod will *not* be used\n                          for the pod to avoid using an unrelated volume by mistake.\n                          Starting the pod is then blocked until the unrelated PVC\n                          is removed. If such a pre-created PVC is meant to be used\n                          by the pod, the PVC has to updated with an owner reference\n                          to the pod once the pod exists. Normally this should not\n                          be necessary, but it may be useful when manually reconstructing\n                          a broken cluster. \\n This field is read-only and no changes\n                          will be made by Kubernetes to the PVC after it has been\n                          created. \\n Required, must not be nil.\"\n                        properties:\n                          metadata:\n                            description: May contain labels and annotations that will\n                              be copied into the PVC when creating it. No other fields\n                              are allowed and will be rejected during validation.\n                            type: object\n                          spec:\n                            description: The specification for the PersistentVolumeClaim.\n                              The entire content is copied unchanged into the PVC\n                              that gets created from this template. The same fields\n                              as in a PersistentVolumeClaim are also valid here.\n                            properties:\n                              accessModes:\n                                description: 'accessModes contains the desired access\n                                  modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                                items:\n                                  type: string\n                                type: array\n                              dataSource:\n                                description: 'dataSource field can be used to specify\n                                  either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                                  * An existing PVC (PersistentVolumeClaim) If the\n                                  provisioner or an external controller can support\n                                  the specified data source, it will create a new\n                                  volume based on the contents of the specified data\n                                  source. When the AnyVolumeDataSource feature gate\n                                  is enabled, dataSource contents will be copied to\n                                  dataSourceRef, and dataSourceRef contents will be\n                                  copied to dataSource when dataSourceRef.namespace\n                                  is not specified. If the namespace is specified,\n                                  then dataSourceRef will not be copied to dataSource.'\n                                properties:\n                                  apiGroup:\n                                    description: APIGroup is the group for the resource\n                                      being referenced. If APIGroup is not specified,\n                                      the specified Kind must be in the core API group.\n                                      For any other third-party types, APIGroup is\n                                      required.\n                                    type: string\n                                  kind:\n                                    description: Kind is the type of resource being\n                                      referenced\n                                    type: string\n                                  name:\n                                    description: Name is the name of resource being\n                                      referenced\n                                    type: string\n                                required:\n                                - kind\n                                - name\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              dataSourceRef:\n                                description: 'dataSourceRef specifies the object from\n                                  which to populate the volume with data, if a non-empty\n                                  volume is desired. This may be any object from a\n                                  non-empty API group (non core object) or a PersistentVolumeClaim\n                                  object. When this field is specified, volume binding\n                                  will only succeed if the type of the specified object\n                                  matches some installed volume populator or dynamic\n                                  provisioner. This field will replace the functionality\n                                  of the dataSource field and as such if both fields\n                                  are non-empty, they must have the same value. For\n                                  backwards compatibility, when namespace isn''t specified\n                                  in dataSourceRef, both fields (dataSource and dataSourceRef)\n                                  will be set to the same value automatically if one\n                                  of them is empty and the other is non-empty. When\n                                  namespace is specified in dataSourceRef, dataSource\n                                  isn''t set to the same value and must be empty.\n                                  There are three important differences between dataSource\n                                  and dataSourceRef: * While dataSource only allows\n                                  two specific types of objects, dataSourceRef allows\n                                  any non-core object, as well as PersistentVolumeClaim\n                                  objects. * While dataSource ignores disallowed values\n                                  (dropping them), dataSourceRef preserves all values,\n                                  and generates an error if a disallowed value is\n                                  specified. * While dataSource only allows local\n                                  objects, dataSourceRef allows objects in any namespaces.\n                                  (Beta) Using this field requires the AnyVolumeDataSource\n                                  feature gate to be enabled. (Alpha) Using the namespace\n                                  field of dataSourceRef requires the CrossNamespaceVolumeDataSource\n                                  feature gate to be enabled.'\n                                properties:\n                                  apiGroup:\n                                    description: APIGroup is the group for the resource\n                                      being referenced. If APIGroup is not specified,\n                                      the specified Kind must be in the core API group.\n                                      For any other third-party types, APIGroup is\n                                      required.\n                                    type: string\n                                  kind:\n                                    description: Kind is the type of resource being\n                                      referenced\n                                    type: string\n                                  name:\n                                    description: Name is the name of resource being\n                                      referenced\n                                    type: string\n                                  namespace:\n                                    description: Namespace is the namespace of resource\n                                      being referenced Note that when a namespace\n                                      is specified, a gateway.networking.k8s.io/ReferenceGrant\n                                      object is required in the referent namespace\n                                      to allow that namespace's owner to accept the\n                                      reference. See the ReferenceGrant documentation\n                                      for details. (Alpha) This field requires the\n                                      CrossNamespaceVolumeDataSource feature gate\n                                      to be enabled.\n                                    type: string\n                                required:\n                                - kind\n                                - name\n                                type: object\n                              resources:\n                                description: 'resources represents the minimum resources\n                                  the volume should have. If RecoverVolumeExpansionFailure\n                                  feature is enabled users are allowed to specify\n                                  resource requirements that are lower than previous\n                                  value but must still be higher than capacity recorded\n                                  in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                                properties:\n                                  claims:\n                                    description: \"Claims lists the names of resources,\n                                      defined in spec.resourceClaims, that are used\n                                      by this container. \\n This is an alpha field\n                                      and requires enabling the DynamicResourceAllocation\n                                      feature gate. \\n This field is immutable. It\n                                      can only be set for containers.\"\n                                    items:\n                                      description: ResourceClaim references one entry\n                                        in PodSpec.ResourceClaims.\n                                      properties:\n                                        name:\n                                          description: Name must match the name of\n                                            one entry in pod.spec.resourceClaims of\n                                            the Pod where this field is used. It makes\n                                            that resource available inside a container.\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Limits describes the maximum amount\n                                      of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Requests describes the minimum amount\n                                      of compute resources required. If Requests is\n                                      omitted for a container, it defaults to Limits\n                                      if that is explicitly specified, otherwise to\n                                      an implementation-defined value. Requests cannot\n                                      exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                    type: object\n                                type: object\n                              selector:\n                                description: selector is a label query over volumes\n                                  to consider for binding.\n                                properties:\n                                  matchExpressions:\n                                    description: matchExpressions is a list of label\n                                      selector requirements. The requirements are\n                                      ANDed.\n                                    items:\n                                      description: A label selector requirement is\n                                        a selector that contains values, a key, and\n                                        an operator that relates the key and values.\n                                      properties:\n                                        key:\n                                          description: key is the label key that the\n                                            selector applies to.\n                                          type: string\n                                        operator:\n                                          description: operator represents a key's\n                                            relationship to a set of values. Valid\n                                            operators are In, NotIn, Exists and DoesNotExist.\n                                          type: string\n                                        values:\n                                          description: values is an array of string\n                                            values. If the operator is In or NotIn,\n                                            the values array must be non-empty. If\n                                            the operator is Exists or DoesNotExist,\n                                            the values array must be empty. This array\n                                            is replaced during a strategic merge patch.\n                                          items:\n                                            type: string\n                                          type: array\n                                      required:\n                                      - key\n                                      - operator\n                                      type: object\n                                    type: array\n                                  matchLabels:\n                                    additionalProperties:\n                                      type: string\n                                    description: matchLabels is a map of {key,value}\n                                      pairs. A single {key,value} in the matchLabels\n                                      map is equivalent to an element of matchExpressions,\n                                      whose key field is \"key\", the operator is \"In\",\n                                      and the values array contains only \"value\".\n                                      The requirements are ANDed.\n                                    type: object\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              storageClassName:\n                                description: 'storageClassName is the name of the\n                                  StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                                type: string\n                              volumeMode:\n                                description: volumeMode defines what type of volume\n                                  is required by the claim. Value of Filesystem is\n                                  implied when not included in claim spec.\n                                type: string\n                              volumeName:\n                                description: volumeName is the binding reference to\n                                  the PersistentVolume backing this claim.\n                                type: string\n                            type: object\n                        required:\n                        - spec\n                        type: object\n                    type: object\n                  volumeClaimTemplate:\n                    description: Defines the PVC spec to be used by the Prometheus\n                      StatefulSets. The easiest way to use a volume that cannot be\n                      automatically provisioned is to use a label selector alongside\n                      manually created PersistentVolumes.\n                    properties:\n                      apiVersion:\n                        description: 'APIVersion defines the versioned schema of this\n                          representation of an object. Servers should convert recognized\n                          schemas to the latest internal value, and may reject unrecognized\n                          values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n                        type: string\n                      kind:\n                        description: 'Kind is a string value representing the REST\n                          resource this object represents. Servers may infer this\n                          from the endpoint the client submits requests to. Cannot\n                          be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n                        type: string\n                      metadata:\n                        description: EmbeddedMetadata contains metadata relevant to\n                          an EmbeddedResource.\n                        properties:\n                          annotations:\n                            additionalProperties:\n                              type: string\n                            description: 'Annotations is an unstructured key value\n                              map stored with a resource that may be set by external\n                              tools to store and retrieve arbitrary metadata. They\n                              are not queryable and should be preserved when modifying\n                              objects. More info: http://kubernetes.io/docs/user-guide/annotations'\n                            type: object\n                          labels:\n                            additionalProperties:\n                              type: string\n                            description: 'Map of string keys and values that can be\n                              used to organize and categorize (scope and select) objects.\n                              May match selectors of replication controllers and services.\n                              More info: http://kubernetes.io/docs/user-guide/labels'\n                            type: object\n                          name:\n                            description: 'Name must be unique within a namespace.\n                              Is required when creating resources, although some resources\n                              may allow a client to request the generation of an appropriate\n                              name automatically. Name is primarily intended for creation\n                              idempotence and configuration definition. Cannot be\n                              updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'\n                            type: string\n                        type: object\n                      spec:\n                        description: 'Defines the desired characteristics of a volume\n                          requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                        properties:\n                          accessModes:\n                            description: 'accessModes contains the desired access\n                              modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                            items:\n                              type: string\n                            type: array\n                          dataSource:\n                            description: 'dataSource field can be used to specify\n                              either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                              * An existing PVC (PersistentVolumeClaim) If the provisioner\n                              or an external controller can support the specified\n                              data source, it will create a new volume based on the\n                              contents of the specified data source. When the AnyVolumeDataSource\n                              feature gate is enabled, dataSource contents will be\n                              copied to dataSourceRef, and dataSourceRef contents\n                              will be copied to dataSource when dataSourceRef.namespace\n                              is not specified. If the namespace is specified, then\n                              dataSourceRef will not be copied to dataSource.'\n                            properties:\n                              apiGroup:\n                                description: APIGroup is the group for the resource\n                                  being referenced. If APIGroup is not specified,\n                                  the specified Kind must be in the core API group.\n                                  For any other third-party types, APIGroup is required.\n                                type: string\n                              kind:\n                                description: Kind is the type of resource being referenced\n                                type: string\n                              name:\n                                description: Name is the name of resource being referenced\n                                type: string\n                            required:\n                            - kind\n                            - name\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          dataSourceRef:\n                            description: 'dataSourceRef specifies the object from\n                              which to populate the volume with data, if a non-empty\n                              volume is desired. This may be any object from a non-empty\n                              API group (non core object) or a PersistentVolumeClaim\n                              object. When this field is specified, volume binding\n                              will only succeed if the type of the specified object\n                              matches some installed volume populator or dynamic provisioner.\n                              This field will replace the functionality of the dataSource\n                              field and as such if both fields are non-empty, they\n                              must have the same value. For backwards compatibility,\n                              when namespace isn''t specified in dataSourceRef, both\n                              fields (dataSource and dataSourceRef) will be set to\n                              the same value automatically if one of them is empty\n                              and the other is non-empty. When namespace is specified\n                              in dataSourceRef, dataSource isn''t set to the same\n                              value and must be empty. There are three important differences\n                              between dataSource and dataSourceRef: * While dataSource\n                              only allows two specific types of objects, dataSourceRef\n                              allows any non-core object, as well as PersistentVolumeClaim\n                              objects. * While dataSource ignores disallowed values\n                              (dropping them), dataSourceRef preserves all values,\n                              and generates an error if a disallowed value is specified.\n                              * While dataSource only allows local objects, dataSourceRef\n                              allows objects in any namespaces. (Beta) Using this\n                              field requires the AnyVolumeDataSource feature gate\n                              to be enabled. (Alpha) Using the namespace field of\n                              dataSourceRef requires the CrossNamespaceVolumeDataSource\n                              feature gate to be enabled.'\n                            properties:\n                              apiGroup:\n                                description: APIGroup is the group for the resource\n                                  being referenced. If APIGroup is not specified,\n                                  the specified Kind must be in the core API group.\n                                  For any other third-party types, APIGroup is required.\n                                type: string\n                              kind:\n                                description: Kind is the type of resource being referenced\n                                type: string\n                              name:\n                                description: Name is the name of resource being referenced\n                                type: string\n                              namespace:\n                                description: Namespace is the namespace of resource\n                                  being referenced Note that when a namespace is specified,\n                                  a gateway.networking.k8s.io/ReferenceGrant object\n                                  is required in the referent namespace to allow that\n                                  namespace's owner to accept the reference. See the\n                                  ReferenceGrant documentation for details. (Alpha)\n                                  This field requires the CrossNamespaceVolumeDataSource\n                                  feature gate to be enabled.\n                                type: string\n                            required:\n                            - kind\n                            - name\n                            type: object\n                          resources:\n                            description: 'resources represents the minimum resources\n                              the volume should have. If RecoverVolumeExpansionFailure\n                              feature is enabled users are allowed to specify resource\n                              requirements that are lower than previous value but\n                              must still be higher than capacity recorded in the status\n                              field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                            properties:\n                              claims:\n                                description: \"Claims lists the names of resources,\n                                  defined in spec.resourceClaims, that are used by\n                                  this container. \\n This is an alpha field and requires\n                                  enabling the DynamicResourceAllocation feature gate.\n                                  \\n This field is immutable. It can only be set for\n                                  containers.\"\n                                items:\n                                  description: ResourceClaim references one entry\n                                    in PodSpec.ResourceClaims.\n                                  properties:\n                                    name:\n                                      description: Name must match the name of one\n                                        entry in pod.spec.resourceClaims of the Pod\n                                        where this field is used. It makes that resource\n                                        available inside a container.\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              limits:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                description: 'Limits describes the maximum amount\n                                  of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                type: object\n                              requests:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                description: 'Requests describes the minimum amount\n                                  of compute resources required. If Requests is omitted\n                                  for a container, it defaults to Limits if that is\n                                  explicitly specified, otherwise to an implementation-defined\n                                  value. Requests cannot exceed Limits. More info:\n                                  https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                type: object\n                            type: object\n                          selector:\n                            description: selector is a label query over volumes to\n                              consider for binding.\n                            properties:\n                              matchExpressions:\n                                description: matchExpressions is a list of label selector\n                                  requirements. The requirements are ANDed.\n                                items:\n                                  description: A label selector requirement is a selector\n                                    that contains values, a key, and an operator that\n                                    relates the key and values.\n                                  properties:\n                                    key:\n                                      description: key is the label key that the selector\n                                        applies to.\n                                      type: string\n                                    operator:\n                                      description: operator represents a key's relationship\n                                        to a set of values. Valid operators are In,\n                                        NotIn, Exists and DoesNotExist.\n                                      type: string\n                                    values:\n                                      description: values is an array of string values.\n                                        If the operator is In or NotIn, the values\n                                        array must be non-empty. If the operator is\n                                        Exists or DoesNotExist, the values array must\n                                        be empty. This array is replaced during a\n                                        strategic merge patch.\n                                      items:\n                                        type: string\n                                      type: array\n                                  required:\n                                  - key\n                                  - operator\n                                  type: object\n                                type: array\n                              matchLabels:\n                                additionalProperties:\n                                  type: string\n                                description: matchLabels is a map of {key,value} pairs.\n                                  A single {key,value} in the matchLabels map is equivalent\n                                  to an element of matchExpressions, whose key field\n                                  is \"key\", the operator is \"In\", and the values array\n                                  contains only \"value\". The requirements are ANDed.\n                                type: object\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          storageClassName:\n                            description: 'storageClassName is the name of the StorageClass\n                              required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                            type: string\n                          volumeMode:\n                            description: volumeMode defines what type of volume is\n                              required by the claim. Value of Filesystem is implied\n                              when not included in claim spec.\n                            type: string\n                          volumeName:\n                            description: volumeName is the binding reference to the\n                              PersistentVolume backing this claim.\n                            type: string\n                        type: object\n                      status:\n                        description: 'Deprecated: this field is never set.'\n                        properties:\n                          accessModes:\n                            description: 'accessModes contains the actual access modes\n                              the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                            items:\n                              type: string\n                            type: array\n                          allocatedResourceStatuses:\n                            additionalProperties:\n                              description: When a controller receives persistentvolume\n                                claim update with ClaimResourceStatus for a resource\n                                that it does not recognizes, then it should ignore\n                                that update and let other controllers handle it.\n                              type: string\n                            description: \"allocatedResourceStatuses stores status\n                              of resource being resized for the given PVC. Key names\n                              follow standard Kubernetes label syntax. Valid values\n                              are either: * Un-prefixed keys: - storage - the capacity\n                              of the volume. * Custom resources must use implementation-defined\n                              prefixed names such as \\\"example.com/my-custom-resource\\\"\n                              Apart from above values - keys that are unprefixed or\n                              have kubernetes.io prefix are considered reserved and\n                              hence may not be used. \\n ClaimResourceStatus can be\n                              in any of following states: - ControllerResizeInProgress:\n                              State set when resize controller starts resizing the\n                              volume in control-plane. - ControllerResizeFailed: State\n                              set when resize has failed in resize controller with\n                              a terminal error. - NodeResizePending: State set when\n                              resize controller has finished resizing the volume but\n                              further resizing of volume is needed on the node. -\n                              NodeResizeInProgress: State set when kubelet starts\n                              resizing the volume. - NodeResizeFailed: State set when\n                              resizing has failed in kubelet with a terminal error.\n                              Transient errors don't set NodeResizeFailed. For example:\n                              if expanding a PVC for more capacity - this field can\n                              be one of the following states: - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"ControllerResizeInProgress\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"ControllerResizeFailed\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizePending\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizeInProgress\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizeFailed\\\" When this field is not set, it\n                              means that no resize operation is in progress for the\n                              given PVC. \\n A controller that receives PVC update\n                              with previously unknown resourceName or ClaimResourceStatus\n                              should ignore the update for the purpose it was designed.\n                              For example - a controller that only is responsible\n                              for resizing capacity of the volume, should ignore PVC\n                              updates that change other valid resources associated\n                              with PVC. \\n This is an alpha field and requires enabling\n                              RecoverVolumeExpansionFailure feature.\"\n                            type: object\n                            x-kubernetes-map-type: granular\n                          allocatedResources:\n                            additionalProperties:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                              x-kubernetes-int-or-string: true\n                            description: \"allocatedResources tracks the resources\n                              allocated to a PVC including its capacity. Key names\n                              follow standard Kubernetes label syntax. Valid values\n                              are either: * Un-prefixed keys: - storage - the capacity\n                              of the volume. * Custom resources must use implementation-defined\n                              prefixed names such as \\\"example.com/my-custom-resource\\\"\n                              Apart from above values - keys that are unprefixed or\n                              have kubernetes.io prefix are considered reserved and\n                              hence may not be used. \\n Capacity reported here may\n                              be larger than the actual capacity when a volume expansion\n                              operation is requested. For storage quota, the larger\n                              value from allocatedResources and PVC.spec.resources\n                              is used. If allocatedResources is not set, PVC.spec.resources\n                              alone is used for quota calculation. If a volume expansion\n                              capacity request is lowered, allocatedResources is only\n                              lowered if there are no expansion operations in progress\n                              and if the actual volume capacity is equal or lower\n                              than the requested capacity. \\n A controller that receives\n                              PVC update with previously unknown resourceName should\n                              ignore the update for the purpose it was designed. For\n                              example - a controller that only is responsible for\n                              resizing capacity of the volume, should ignore PVC updates\n                              that change other valid resources associated with PVC.\n                              \\n This is an alpha field and requires enabling RecoverVolumeExpansionFailure\n                              feature.\"\n                            type: object\n                          capacity:\n                            additionalProperties:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                              x-kubernetes-int-or-string: true\n                            description: capacity represents the actual resources\n                              of the underlying volume.\n                            type: object\n                          conditions:\n                            description: conditions is the current Condition of persistent\n                              volume claim. If underlying persistent volume is being\n                              resized then the Condition will be set to 'ResizeStarted'.\n                            items:\n                              description: PersistentVolumeClaimCondition contains\n                                details about state of pvc\n                              properties:\n                                lastProbeTime:\n                                  description: lastProbeTime is the time we probed\n                                    the condition.\n                                  format: date-time\n                                  type: string\n                                lastTransitionTime:\n                                  description: lastTransitionTime is the time the\n                                    condition transitioned from one status to another.\n                                  format: date-time\n                                  type: string\n                                message:\n                                  description: message is the human-readable message\n                                    indicating details about last transition.\n                                  type: string\n                                reason:\n                                  description: reason is a unique, this should be\n                                    a short, machine understandable string that gives\n                                    the reason for condition's last transition. If\n                                    it reports \"ResizeStarted\" that means the underlying\n                                    persistent volume is being resized.\n                                  type: string\n                                status:\n                                  type: string\n                                type:\n                                  description: PersistentVolumeClaimConditionType\n                                    is a valid value of PersistentVolumeClaimCondition.Type\n                                  type: string\n                              required:\n                              - status\n                              - type\n                              type: object\n                            type: array\n                          phase:\n                            description: phase represents the current phase of PersistentVolumeClaim.\n                            type: string\n                        type: object\n                    type: object\n                type: object\n              tag:\n                description: 'Deprecated: use ''spec.image'' instead. The image''s\n                  tag can be specified as part of the image name.'\n                type: string\n              targetLimit:\n                description: TargetLimit defines a limit on the number of scraped\n                  targets that will be accepted. Only valid in Prometheus versions\n                  2.45.0 and newer.\n                format: int64\n                type: integer\n              thanos:\n                description: \"Defines the configuration of the optional Thanos sidecar.\n                  \\n This section is experimental, it may change significantly without\n                  deprecation notice in any release.\"\n                properties:\n                  additionalArgs:\n                    description: AdditionalArgs allows setting additional arguments\n                      for the Thanos container. The arguments are passed as-is to\n                      the Thanos container which may cause issues if they are invalid\n                      or not supported the given Thanos version. In case of an argument\n                      conflict (e.g. an argument which is already set by the operator\n                      itself) or when providing an invalid argument, the reconciliation\n                      will fail and an error will be logged.\n                    items:\n                      description: Argument as part of the AdditionalArgs list.\n                      properties:\n                        name:\n                          description: Name of the argument, e.g. \"scrape.discovery-reload-interval\".\n                          minLength: 1\n                          type: string\n                        value:\n                          description: Argument value, e.g. 30s. Can be empty for\n                            name-only arguments (e.g. --storage.tsdb.no-lockfile)\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    type: array\n                  baseImage:\n                    description: 'Deprecated: use ''image'' instead.'\n                    type: string\n                  blockSize:\n                    default: 2h\n                    description: \"BlockDuration controls the size of TSDB blocks produced\n                      by Prometheus. The default value is 2h to match the upstream\n                      Prometheus defaults. \\n WARNING: Changing the block duration\n                      can impact the performance and efficiency of the entire Prometheus/Thanos\n                      stack due to how it interacts with memory and Thanos compactors.\n                      It is recommended to keep this value set to a multiple of 120\n                      times your longest scrape or rule interval. For example, 30s\n                      * 120 = 1h.\"\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                  getConfigInterval:\n                    description: How often to retrieve the Prometheus configuration.\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                  getConfigTimeout:\n                    description: Maximum time to wait when retrieving the Prometheus\n                      configuration.\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                  grpcListenLocal:\n                    description: \"When true, the Thanos sidecar listens on the loopback\n                      interface instead of the Pod IP's address for the gRPC endpoints.\n                      \\n It has no effect if `listenLocal` is true.\"\n                    type: boolean\n                  grpcServerTlsConfig:\n                    description: \"Configures the TLS parameters for the gRPC server\n                      providing the StoreAPI. \\n Note: Currently only the `caFile`,\n                      `certFile`, and `keyFile` fields are supported.\"\n                    properties:\n                      ca:\n                        description: Certificate authority used when verifying server\n                          certificates.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      caFile:\n                        description: Path to the CA cert in the Prometheus container\n                          to use for the targets.\n                        type: string\n                      cert:\n                        description: Client certificate to present when doing client-authentication.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      certFile:\n                        description: Path to the client cert file in the Prometheus\n                          container for the targets.\n                        type: string\n                      insecureSkipVerify:\n                        description: Disable target certificate validation.\n                        type: boolean\n                      keyFile:\n                        description: Path to the client key file in the Prometheus\n                          container for the targets.\n                        type: string\n                      keySecret:\n                        description: Secret containing the client key file for the\n                          targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      serverName:\n                        description: Used to verify the hostname for the targets.\n                        type: string\n                    type: object\n                  httpListenLocal:\n                    description: \"When true, the Thanos sidecar listens on the loopback\n                      interface instead of the Pod IP's address for the HTTP endpoints.\n                      \\n It has no effect if `listenLocal` is true.\"\n                    type: boolean\n                  image:\n                    description: \"Container image name for Thanos. If specified, it\n                      takes precedence over the `spec.thanos.baseImage`, `spec.thanos.tag`\n                      and `spec.thanos.sha` fields. \\n Specifying `spec.thanos.version`\n                      is still necessary to ensure the Prometheus Operator knows which\n                      version of Thanos is being configured. \\n If neither `spec.thanos.image`\n                      nor `spec.thanos.baseImage` are defined, the operator will use\n                      the latest upstream version of Thanos available at the time\n                      when the operator was released.\"\n                    type: string\n                  listenLocal:\n                    description: 'Deprecated: use `grpcListenLocal` and `httpListenLocal`\n                      instead.'\n                    type: boolean\n                  logFormat:\n                    description: Log format for the Thanos sidecar.\n                    enum:\n                    - \"\"\n                    - logfmt\n                    - json\n                    type: string\n                  logLevel:\n                    description: Log level for the Thanos sidecar.\n                    enum:\n                    - \"\"\n                    - debug\n                    - info\n                    - warn\n                    - error\n                    type: string\n                  minTime:\n                    description: Defines the start of time range limit served by the\n                      Thanos sidecar's StoreAPI. The field's value should be a constant\n                      time in RFC3339 format or a time duration relative to current\n                      time, such as -1d or 2h45m. Valid duration units are ms, s,\n                      m, h, d, w, y.\n                    type: string\n                  objectStorageConfig:\n                    description: \"Defines the Thanos sidecar's configuration to upload\n                      TSDB blocks to object storage. \\n More info: https://thanos.io/tip/thanos/storage.md/\n                      \\n objectStorageConfigFile takes precedence over this field.\"\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  objectStorageConfigFile:\n                    description: \"Defines the Thanos sidecar's configuration file\n                      to upload TSDB blocks to object storage. \\n More info: https://thanos.io/tip/thanos/storage.md/\n                      \\n This field takes precedence over objectStorageConfig.\"\n                    type: string\n                  readyTimeout:\n                    description: ReadyTimeout is the maximum time that the Thanos\n                      sidecar will wait for Prometheus to start.\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                  resources:\n                    description: Defines the resources requests and limits of the\n                      Thanos sidecar.\n                    properties:\n                      claims:\n                        description: \"Claims lists the names of resources, defined\n                          in spec.resourceClaims, that are used by this container.\n                          \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                          feature gate. \\n This field is immutable. It can only be\n                          set for containers.\"\n                        items:\n                          description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                          properties:\n                            name:\n                              description: Name must match the name of one entry in\n                                pod.spec.resourceClaims of the Pod where this field\n                                is used. It makes that resource available inside a\n                                container.\n                              type: string\n                          required:\n                          - name\n                          type: object\n                        type: array\n                        x-kubernetes-list-map-keys:\n                        - name\n                        x-kubernetes-list-type: map\n                      limits:\n                        additionalProperties:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                        description: 'Limits describes the maximum amount of compute\n                          resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                        type: object\n                      requests:\n                        additionalProperties:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                        description: 'Requests describes the minimum amount of compute\n                          resources required. If Requests is omitted for a container,\n                          it defaults to Limits if that is explicitly specified, otherwise\n                          to an implementation-defined value. Requests cannot exceed\n                          Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                        type: object\n                    type: object\n                  sha:\n                    description: 'Deprecated: use ''image'' instead.  The image digest\n                      can be specified as part of the image name.'\n                    type: string\n                  tag:\n                    description: 'Deprecated: use ''image'' instead. The image''s\n                      tag can be specified as as part of the image name.'\n                    type: string\n                  tracingConfig:\n                    description: \"Defines the tracing configuration for the Thanos\n                      sidecar. \\n More info: https://thanos.io/tip/thanos/tracing.md/\n                      \\n This is an experimental feature, it may change in any upcoming\n                      release in a breaking way. \\n tracingConfigFile takes precedence\n                      over this field.\"\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  tracingConfigFile:\n                    description: \"Defines the tracing configuration file for the Thanos\n                      sidecar. \\n More info: https://thanos.io/tip/thanos/tracing.md/\n                      \\n This is an experimental feature, it may change in any upcoming\n                      release in a breaking way. \\n This field takes precedence over\n                      tracingConfig.\"\n                    type: string\n                  version:\n                    description: \"Version of Thanos being deployed. The operator uses\n                      this information to generate the Prometheus StatefulSet + configuration\n                      files. \\n If not specified, the operator assumes the latest\n                      upstream release of Thanos available at the time when the version\n                      of the operator was released.\"\n                    type: string\n                  volumeMounts:\n                    description: VolumeMounts allows configuration of additional VolumeMounts\n                      for Thanos. VolumeMounts specified will be appended to other\n                      VolumeMounts in the 'thanos-sidecar' container.\n                    items:\n                      description: VolumeMount describes a mounting of a Volume within\n                        a container.\n                      properties:\n                        mountPath:\n                          description: Path within the container at which the volume\n                            should be mounted.  Must not contain ':'.\n                          type: string\n                        mountPropagation:\n                          description: mountPropagation determines how mounts are\n                            propagated from the host to container and the other way\n                            around. When not set, MountPropagationNone is used. This\n                            field is beta in 1.10.\n                          type: string\n                        name:\n                          description: This must match the Name of a Volume.\n                          type: string\n                        readOnly:\n                          description: Mounted read-only if true, read-write otherwise\n                            (false or unspecified). Defaults to false.\n                          type: boolean\n                        subPath:\n                          description: Path within the volume from which the container's\n                            volume should be mounted. Defaults to \"\" (volume's root).\n                          type: string\n                        subPathExpr:\n                          description: Expanded path within the volume from which\n                            the container's volume should be mounted. Behaves similarly\n                            to SubPath but environment variable references $(VAR_NAME)\n                            are expanded using the container's environment. Defaults\n                            to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                            exclusive.\n                          type: string\n                      required:\n                      - mountPath\n                      - name\n                      type: object\n                    type: array\n                type: object\n              tolerations:\n                description: Defines the Pods' tolerations if specified.\n                items:\n                  description: The pod this Toleration is attached to tolerates any\n                    taint that matches the triple <key,value,effect> using the matching\n                    operator <operator>.\n                  properties:\n                    effect:\n                      description: Effect indicates the taint effect to match. Empty\n                        means match all taint effects. When specified, allowed values\n                        are NoSchedule, PreferNoSchedule and NoExecute.\n                      type: string\n                    key:\n                      description: Key is the taint key that the toleration applies\n                        to. Empty means match all taint keys. If the key is empty,\n                        operator must be Exists; this combination means to match all\n                        values and all keys.\n                      type: string\n                    operator:\n                      description: Operator represents a key's relationship to the\n                        value. Valid operators are Exists and Equal. Defaults to Equal.\n                        Exists is equivalent to wildcard for value, so that a pod\n                        can tolerate all taints of a particular category.\n                      type: string\n                    tolerationSeconds:\n                      description: TolerationSeconds represents the period of time\n                        the toleration (which must be of effect NoExecute, otherwise\n                        this field is ignored) tolerates the taint. By default, it\n                        is not set, which means tolerate the taint forever (do not\n                        evict). Zero and negative values will be treated as 0 (evict\n                        immediately) by the system.\n                      format: int64\n                      type: integer\n                    value:\n                      description: Value is the taint value the toleration matches\n                        to. If the operator is Exists, the value should be empty,\n                        otherwise just a regular string.\n                      type: string\n                  type: object\n                type: array\n              topologySpreadConstraints:\n                description: Defines the pod's topology spread constraints if specified.\n                items:\n                  properties:\n                    additionalLabelSelectors:\n                      description: Defines what Prometheus Operator managed labels\n                        should be added to labelSelector on the topologySpreadConstraint.\n                      enum:\n                      - OnResource\n                      - OnShard\n                      type: string\n                    labelSelector:\n                      description: LabelSelector is used to find matching pods. Pods\n                        that match this label selector are counted to determine the\n                        number of pods in their corresponding topology domain.\n                      properties:\n                        matchExpressions:\n                          description: matchExpressions is a list of label selector\n                            requirements. The requirements are ANDed.\n                          items:\n                            description: A label selector requirement is a selector\n                              that contains values, a key, and an operator that relates\n                              the key and values.\n                            properties:\n                              key:\n                                description: key is the label key that the selector\n                                  applies to.\n                                type: string\n                              operator:\n                                description: operator represents a key's relationship\n                                  to a set of values. Valid operators are In, NotIn,\n                                  Exists and DoesNotExist.\n                                type: string\n                              values:\n                                description: values is an array of string values.\n                                  If the operator is In or NotIn, the values array\n                                  must be non-empty. If the operator is Exists or\n                                  DoesNotExist, the values array must be empty. This\n                                  array is replaced during a strategic merge patch.\n                                items:\n                                  type: string\n                                type: array\n                            required:\n                            - key\n                            - operator\n                            type: object\n                          type: array\n                        matchLabels:\n                          additionalProperties:\n                            type: string\n                          description: matchLabels is a map of {key,value} pairs.\n                            A single {key,value} in the matchLabels map is equivalent\n                            to an element of matchExpressions, whose key field is\n                            \"key\", the operator is \"In\", and the values array contains\n                            only \"value\". The requirements are ANDed.\n                          type: object\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    matchLabelKeys:\n                      description: \"MatchLabelKeys is a set of pod label keys to select\n                        the pods over which spreading will be calculated. The keys\n                        are used to lookup values from the incoming pod labels, those\n                        key-value labels are ANDed with labelSelector to select the\n                        group of existing pods over which spreading will be calculated\n                        for the incoming pod. The same key is forbidden to exist in\n                        both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot\n                        be set when LabelSelector isn't set. Keys that don't exist\n                        in the incoming pod labels will be ignored. A null or empty\n                        list means only match against labelSelector. \\n This is a\n                        beta field and requires the MatchLabelKeysInPodTopologySpread\n                        feature gate to be enabled (enabled by default).\"\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    maxSkew:\n                      description: 'MaxSkew describes the degree to which pods may\n                        be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`,\n                        it is the maximum permitted difference between the number\n                        of matching pods in the target topology and the global minimum.\n                        The global minimum is the minimum number of matching pods\n                        in an eligible domain or zero if the number of eligible domains\n                        is less than MinDomains. For example, in a 3-zone cluster,\n                        MaxSkew is set to 1, and pods with the same labelSelector\n                        spread as 2/2/1: In this case, the global minimum is 1. |\n                        zone1 | zone2 | zone3 | |  P P  |  P P  |   P   | - if MaxSkew\n                        is 1, incoming pod can only be scheduled to zone3 to become\n                        2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1)\n                        on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming\n                        pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`,\n                        it is used to give higher precedence to topologies that satisfy\n                        it. It''s a required field. Default value is 1 and 0 is not\n                        allowed.'\n                      format: int32\n                      type: integer\n                    minDomains:\n                      description: \"MinDomains indicates a minimum number of eligible\n                        domains. When the number of eligible domains with matching\n                        topology keys is less than minDomains, Pod Topology Spread\n                        treats \\\"global minimum\\\" as 0, and then the calculation of\n                        Skew is performed. And when the number of eligible domains\n                        with matching topology keys equals or greater than minDomains,\n                        this value has no effect on scheduling. As a result, when\n                        the number of eligible domains is less than minDomains, scheduler\n                        won't schedule more than maxSkew Pods to those domains. If\n                        value is nil, the constraint behaves as if MinDomains is equal\n                        to 1. Valid values are integers greater than 0. When value\n                        is not nil, WhenUnsatisfiable must be DoNotSchedule. \\n For\n                        example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains\n                        is set to 5 and pods with the same labelSelector spread as\n                        2/2/2: | zone1 | zone2 | zone3 | |  P P  |  P P  |  P P  |\n                        The number of domains is less than 5(MinDomains), so \\\"global\n                        minimum\\\" is treated as 0. In this situation, new pod with\n                        the same labelSelector cannot be scheduled, because computed\n                        skew will be 3(3 - 0) if new Pod is scheduled to any of the\n                        three zones, it will violate MaxSkew. \\n This is a beta field\n                        and requires the MinDomainsInPodTopologySpread feature gate\n                        to be enabled (enabled by default).\"\n                      format: int32\n                      type: integer\n                    nodeAffinityPolicy:\n                      description: \"NodeAffinityPolicy indicates how we will treat\n                        Pod's nodeAffinity/nodeSelector when calculating pod topology\n                        spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector\n                        are included in the calculations. - Ignore: nodeAffinity/nodeSelector\n                        are ignored. All nodes are included in the calculations. \\n\n                        If this value is nil, the behavior is equivalent to the Honor\n                        policy. This is a beta-level feature default enabled by the\n                        NodeInclusionPolicyInPodTopologySpread feature flag.\"\n                      type: string\n                    nodeTaintsPolicy:\n                      description: \"NodeTaintsPolicy indicates how we will treat node\n                        taints when calculating pod topology spread skew. Options\n                        are: - Honor: nodes without taints, along with tainted nodes\n                        for which the incoming pod has a toleration, are included.\n                        - Ignore: node taints are ignored. All nodes are included.\n                        \\n If this value is nil, the behavior is equivalent to the\n                        Ignore policy. This is a beta-level feature default enabled\n                        by the NodeInclusionPolicyInPodTopologySpread feature flag.\"\n                      type: string\n                    topologyKey:\n                      description: TopologyKey is the key of node labels. Nodes that\n                        have a label with this key and identical values are considered\n                        to be in the same topology. We consider each <key, value>\n                        as a \"bucket\", and try to put balanced number of pods into\n                        each bucket. We define a domain as a particular instance of\n                        a topology. Also, we define an eligible domain as a domain\n                        whose nodes meet the requirements of nodeAffinityPolicy and\n                        nodeTaintsPolicy. e.g. If TopologyKey is \"kubernetes.io/hostname\",\n                        each Node is a domain of that topology. And, if TopologyKey\n                        is \"topology.kubernetes.io/zone\", each zone is a domain of\n                        that topology. It's a required field.\n                      type: string\n                    whenUnsatisfiable:\n                      description: 'WhenUnsatisfiable indicates how to deal with a\n                        pod if it doesn''t satisfy the spread constraint. - DoNotSchedule\n                        (default) tells the scheduler not to schedule it. - ScheduleAnyway\n                        tells the scheduler to schedule the pod in any location, but\n                        giving higher precedence to topologies that would help reduce\n                        the skew. A constraint is considered \"Unsatisfiable\" for an\n                        incoming pod if and only if every possible node assignment\n                        for that pod would violate \"MaxSkew\" on some topology. For\n                        example, in a 3-zone cluster, MaxSkew is set to 1, and pods\n                        with the same labelSelector spread as 3/1/1: | zone1 | zone2\n                        | zone3 | | P P P |   P   |   P   | If WhenUnsatisfiable is\n                        set to DoNotSchedule, incoming pod can only be scheduled to\n                        zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on\n                        zone2(zone3) satisfies MaxSkew(1). In other words, the cluster\n                        can still be imbalanced, but scheduler won''t make it *more*\n                        imbalanced. It''s a required field.'\n                      type: string\n                  required:\n                  - maxSkew\n                  - topologyKey\n                  - whenUnsatisfiable\n                  type: object\n                type: array\n              tracingConfig:\n                description: 'EXPERIMENTAL: TracingConfig configures tracing in Prometheus.\n                  This is an experimental feature, it may change in any upcoming release\n                  in a breaking way.'\n                properties:\n                  clientType:\n                    description: Client used to export the traces. Supported values\n                      are `http` or `grpc`.\n                    enum:\n                    - http\n                    - grpc\n                    type: string\n                  compression:\n                    description: Compression key for supported compression types.\n                      The only supported value is `gzip`.\n                    enum:\n                    - gzip\n                    type: string\n                  endpoint:\n                    description: Endpoint to send the traces to. Should be provided\n                      in format <host>:<port>.\n                    minLength: 1\n                    type: string\n                  headers:\n                    additionalProperties:\n                      type: string\n                    description: Key-value pairs to be used as headers associated\n                      with gRPC or HTTP requests.\n                    type: object\n                  insecure:\n                    description: If disabled, the client will use a secure connection.\n                    type: boolean\n                  samplingFraction:\n                    anyOf:\n                    - type: integer\n                    - type: string\n                    description: Sets the probability a given trace will be sampled.\n                      Must be a float from 0 through 1.\n                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                    x-kubernetes-int-or-string: true\n                  timeout:\n                    description: Maximum time the exporter will wait for each batch\n                      export.\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                  tlsConfig:\n                    description: TLS Config to use when sending traces.\n                    properties:\n                      ca:\n                        description: Certificate authority used when verifying server\n                          certificates.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      caFile:\n                        description: Path to the CA cert in the Prometheus container\n                          to use for the targets.\n                        type: string\n                      cert:\n                        description: Client certificate to present when doing client-authentication.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      certFile:\n                        description: Path to the client cert file in the Prometheus\n                          container for the targets.\n                        type: string\n                      insecureSkipVerify:\n                        description: Disable target certificate validation.\n                        type: boolean\n                      keyFile:\n                        description: Path to the client key file in the Prometheus\n                          container for the targets.\n                        type: string\n                      keySecret:\n                        description: Secret containing the client key file for the\n                          targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      serverName:\n                        description: Used to verify the hostname for the targets.\n                        type: string\n                    type: object\n                required:\n                - endpoint\n                type: object\n              tsdb:\n                description: Defines the runtime reloadable configuration of the timeseries\n                  database (TSDB).\n                properties:\n                  outOfOrderTimeWindow:\n                    description: \"Configures how old an out-of-order/out-of-bounds\n                      sample can be with respect to the TSDB max time. \\n An out-of-order/out-of-bounds\n                      sample is ingested into the TSDB as long as the timestamp of\n                      the sample is >= (TSDB.MaxTime - outOfOrderTimeWindow). \\n Out\n                      of order ingestion is an experimental feature. \\n It requires\n                      Prometheus >= v2.39.0.\"\n                    pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                    type: string\n                type: object\n              version:\n                description: \"Version of Prometheus being deployed. The operator uses\n                  this information to generate the Prometheus StatefulSet + configuration\n                  files. \\n If not specified, the operator assumes the latest upstream\n                  version of Prometheus available at the time when the version of\n                  the operator was released.\"\n                type: string\n              volumeMounts:\n                description: \"VolumeMounts allows the configuration of additional\n                  VolumeMounts. \\n VolumeMounts will be appended to other VolumeMounts\n                  in the 'prometheus' container, that are generated as a result of\n                  StorageSpec objects.\"\n                items:\n                  description: VolumeMount describes a mounting of a Volume within\n                    a container.\n                  properties:\n                    mountPath:\n                      description: Path within the container at which the volume should\n                        be mounted.  Must not contain ':'.\n                      type: string\n                    mountPropagation:\n                      description: mountPropagation determines how mounts are propagated\n                        from the host to container and the other way around. When\n                        not set, MountPropagationNone is used. This field is beta\n                        in 1.10.\n                      type: string\n                    name:\n                      description: This must match the Name of a Volume.\n                      type: string\n                    readOnly:\n                      description: Mounted read-only if true, read-write otherwise\n                        (false or unspecified). Defaults to false.\n                      type: boolean\n                    subPath:\n                      description: Path within the volume from which the container's\n                        volume should be mounted. Defaults to \"\" (volume's root).\n                      type: string\n                    subPathExpr:\n                      description: Expanded path within the volume from which the\n                        container's volume should be mounted. Behaves similarly to\n                        SubPath but environment variable references $(VAR_NAME) are\n                        expanded using the container's environment. Defaults to \"\"\n                        (volume's root). SubPathExpr and SubPath are mutually exclusive.\n                      type: string\n                  required:\n                  - mountPath\n                  - name\n                  type: object\n                type: array\n              volumes:\n                description: Volumes allows the configuration of additional volumes\n                  on the output StatefulSet definition. Volumes specified will be\n                  appended to other volumes that are generated as a result of StorageSpec\n                  objects.\n                items:\n                  description: Volume represents a named volume in a pod that may\n                    be accessed by any container in the pod.\n                  properties:\n                    awsElasticBlockStore:\n                      description: 'awsElasticBlockStore represents an AWS Disk resource\n                        that is attached to a kubelet''s host machine and then exposed\n                        to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        partition:\n                          description: 'partition is the partition in the volume that\n                            you want to mount. If omitted, the default is to mount\n                            by volume name. Examples: For volume /dev/sda1, you specify\n                            the partition as \"1\". Similarly, the volume partition\n                            for /dev/sda is \"0\" (or you can leave the property empty).'\n                          format: int32\n                          type: integer\n                        readOnly:\n                          description: 'readOnly value true will force the readOnly\n                            setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                          type: boolean\n                        volumeID:\n                          description: 'volumeID is unique ID of the persistent disk\n                            resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    azureDisk:\n                      description: azureDisk represents an Azure Data Disk mount on\n                        the host and bind mount to the pod.\n                      properties:\n                        cachingMode:\n                          description: 'cachingMode is the Host Caching mode: None,\n                            Read Only, Read Write.'\n                          type: string\n                        diskName:\n                          description: diskName is the Name of the data disk in the\n                            blob storage\n                          type: string\n                        diskURI:\n                          description: diskURI is the URI of data disk in the blob\n                            storage\n                          type: string\n                        fsType:\n                          description: fsType is Filesystem type to mount. Must be\n                            a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        kind:\n                          description: 'kind expected values are Shared: multiple\n                            blob disks per storage account  Dedicated: single blob\n                            disk per storage account  Managed: azure managed data\n                            disk (only in managed availability set). defaults to shared'\n                          type: string\n                        readOnly:\n                          description: readOnly Defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                      required:\n                      - diskName\n                      - diskURI\n                      type: object\n                    azureFile:\n                      description: azureFile represents an Azure File Service mount\n                        on the host and bind mount to the pod.\n                      properties:\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretName:\n                          description: secretName is the  name of secret that contains\n                            Azure Storage Account Name and Key\n                          type: string\n                        shareName:\n                          description: shareName is the azure share Name\n                          type: string\n                      required:\n                      - secretName\n                      - shareName\n                      type: object\n                    cephfs:\n                      description: cephFS represents a Ceph FS mount on the host that\n                        shares a pod's lifetime\n                      properties:\n                        monitors:\n                          description: 'monitors is Required: Monitors is a collection\n                            of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          items:\n                            type: string\n                          type: array\n                        path:\n                          description: 'path is Optional: Used as the mounted root,\n                            rather than the full Ceph tree, default is /'\n                          type: string\n                        readOnly:\n                          description: 'readOnly is Optional: Defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: boolean\n                        secretFile:\n                          description: 'secretFile is Optional: SecretFile is the\n                            path to key ring for User, default is /etc/ceph/user.secret\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: string\n                        secretRef:\n                          description: 'secretRef is Optional: SecretRef is reference\n                            to the authentication secret for User, default is empty.\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        user:\n                          description: 'user is optional: User is the rados user name,\n                            default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: string\n                      required:\n                      - monitors\n                      type: object\n                    cinder:\n                      description: 'cinder represents a cinder volume attached and\n                        mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to\n                            be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: string\n                        readOnly:\n                          description: 'readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                            More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is optional: points to a secret\n                            object containing parameters used to connect to OpenStack.'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        volumeID:\n                          description: 'volumeID used to identify the volume in cinder.\n                            More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    configMap:\n                      description: configMap represents a configMap that should populate\n                        this volume\n                      properties:\n                        defaultMode:\n                          description: 'defaultMode is optional: mode bits used to\n                            set permissions on created files by default. Must be an\n                            octal value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: items if unspecified, each key-value pair in\n                            the Data field of the referenced ConfigMap will be projected\n                            into the volume as a file whose name is the key and content\n                            is the value. If specified, the listed keys will be projected\n                            into the specified paths, and unlisted keys will not be\n                            present. If a key is specified which is not present in\n                            the ConfigMap, the volume setup will error unless it is\n                            marked optional. Paths must be relative and may not contain\n                            the '..' path or start with '..'.\n                          items:\n                            description: Maps a string key to a path within a volume.\n                            properties:\n                              key:\n                                description: key is the key to project.\n                                type: string\n                              mode:\n                                description: 'mode is Optional: mode bits used to\n                                  set permissions on this file. Must be an octal value\n                                  between 0000 and 0777 or a decimal value between\n                                  0 and 511. YAML accepts both octal and decimal values,\n                                  JSON requires decimal values for mode bits. If not\n                                  specified, the volume defaultMode will be used.\n                                  This might be in conflict with other options that\n                                  affect the file mode, like fsGroup, and the result\n                                  can be other mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: path is the relative path of the file\n                                  to map the key to. May not be an absolute path.\n                                  May not contain the path element '..'. May not start\n                                  with the string '..'.\n                                type: string\n                            required:\n                            - key\n                            - path\n                            type: object\n                          type: array\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: optional specify whether the ConfigMap or its\n                            keys must be defined\n                          type: boolean\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    csi:\n                      description: csi (Container Storage Interface) represents ephemeral\n                        storage that is handled by certain external CSI drivers (Beta\n                        feature).\n                      properties:\n                        driver:\n                          description: driver is the name of the CSI driver that handles\n                            this volume. Consult with your admin for the correct name\n                            as registered in the cluster.\n                          type: string\n                        fsType:\n                          description: fsType to mount. Ex. \"ext4\", \"xfs\", \"ntfs\".\n                            If not provided, the empty value is passed to the associated\n                            CSI driver which will determine the default filesystem\n                            to apply.\n                          type: string\n                        nodePublishSecretRef:\n                          description: nodePublishSecretRef is a reference to the\n                            secret object containing sensitive information to pass\n                            to the CSI driver to complete the CSI NodePublishVolume\n                            and NodeUnpublishVolume calls. This field is optional,\n                            and  may be empty if no secret is required. If the secret\n                            object contains more than one secret, all secret references\n                            are passed.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        readOnly:\n                          description: readOnly specifies a read-only configuration\n                            for the volume. Defaults to false (read/write).\n                          type: boolean\n                        volumeAttributes:\n                          additionalProperties:\n                            type: string\n                          description: volumeAttributes stores driver-specific properties\n                            that are passed to the CSI driver. Consult your driver's\n                            documentation for supported values.\n                          type: object\n                      required:\n                      - driver\n                      type: object\n                    downwardAPI:\n                      description: downwardAPI represents downward API about the pod\n                        that should populate this volume\n                      properties:\n                        defaultMode:\n                          description: 'Optional: mode bits to use on created files\n                            by default. Must be a Optional: mode bits used to set\n                            permissions on created files by default. Must be an octal\n                            value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: Items is a list of downward API volume file\n                          items:\n                            description: DownwardAPIVolumeFile represents information\n                              to create the file containing the pod field\n                            properties:\n                              fieldRef:\n                                description: 'Required: Selects a field of the pod:\n                                  only annotations, labels, name and namespace are\n                                  supported.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              mode:\n                                description: 'Optional: mode bits used to set permissions\n                                  on this file, must be an octal value between 0000\n                                  and 0777 or a decimal value between 0 and 511. YAML\n                                  accepts both octal and decimal values, JSON requires\n                                  decimal values for mode bits. If not specified,\n                                  the volume defaultMode will be used. This might\n                                  be in conflict with other options that affect the\n                                  file mode, like fsGroup, and the result can be other\n                                  mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: 'Required: Path is  the relative path\n                                  name of the file to be created. Must not be absolute\n                                  or contain the ''..'' path. Must be utf-8 encoded.\n                                  The first item of the relative path must not start\n                                  with ''..'''\n                                type: string\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, requests.cpu and requests.memory)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            required:\n                            - path\n                            type: object\n                          type: array\n                      type: object\n                    emptyDir:\n                      description: 'emptyDir represents a temporary directory that\n                        shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                      properties:\n                        medium:\n                          description: 'medium represents what type of storage medium\n                            should back this directory. The default is \"\" which means\n                            to use the node''s default medium. Must be an empty string\n                            (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                          type: string\n                        sizeLimit:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          description: 'sizeLimit is the total amount of local storage\n                            required for this EmptyDir volume. The size limit is also\n                            applicable for memory medium. The maximum usage on memory\n                            medium EmptyDir would be the minimum value between the\n                            SizeLimit specified here and the sum of memory limits\n                            of all containers in a pod. The default is nil which means\n                            that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                      type: object\n                    ephemeral:\n                      description: \"ephemeral represents a volume that is handled\n                        by a cluster storage driver. The volume's lifecycle is tied\n                        to the pod that defines it - it will be created before the\n                        pod starts, and deleted when the pod is removed. \\n Use this\n                        if: a) the volume is only needed while the pod runs, b) features\n                        of normal volumes like restoring from snapshot or capacity\n                        tracking are needed, c) the storage driver is specified through\n                        a storage class, and d) the storage driver supports dynamic\n                        volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource\n                        for more information on the connection between this volume\n                        type and PersistentVolumeClaim). \\n Use PersistentVolumeClaim\n                        or one of the vendor-specific APIs for volumes that persist\n                        for longer than the lifecycle of an individual pod. \\n Use\n                        CSI for light-weight local ephemeral volumes if the CSI driver\n                        is meant to be used that way - see the documentation of the\n                        driver for more information. \\n A pod can use both types of\n                        ephemeral volumes and persistent volumes at the same time.\"\n                      properties:\n                        volumeClaimTemplate:\n                          description: \"Will be used to create a stand-alone PVC to\n                            provision the volume. The pod in which this EphemeralVolumeSource\n                            is embedded will be the owner of the PVC, i.e. the PVC\n                            will be deleted together with the pod.  The name of the\n                            PVC will be `<pod name>-<volume name>` where `<volume\n                            name>` is the name from the `PodSpec.Volumes` array entry.\n                            Pod validation will reject the pod if the concatenated\n                            name is not valid for a PVC (for example, too long). \\n\n                            An existing PVC with that name that is not owned by the\n                            pod will *not* be used for the pod to avoid using an unrelated\n                            volume by mistake. Starting the pod is then blocked until\n                            the unrelated PVC is removed. If such a pre-created PVC\n                            is meant to be used by the pod, the PVC has to updated\n                            with an owner reference to the pod once the pod exists.\n                            Normally this should not be necessary, but it may be useful\n                            when manually reconstructing a broken cluster. \\n This\n                            field is read-only and no changes will be made by Kubernetes\n                            to the PVC after it has been created. \\n Required, must\n                            not be nil.\"\n                          properties:\n                            metadata:\n                              description: May contain labels and annotations that\n                                will be copied into the PVC when creating it. No other\n                                fields are allowed and will be rejected during validation.\n                              type: object\n                            spec:\n                              description: The specification for the PersistentVolumeClaim.\n                                The entire content is copied unchanged into the PVC\n                                that gets created from this template. The same fields\n                                as in a PersistentVolumeClaim are also valid here.\n                              properties:\n                                accessModes:\n                                  description: 'accessModes contains the desired access\n                                    modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                                  items:\n                                    type: string\n                                  type: array\n                                dataSource:\n                                  description: 'dataSource field can be used to specify\n                                    either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                                    * An existing PVC (PersistentVolumeClaim) If the\n                                    provisioner or an external controller can support\n                                    the specified data source, it will create a new\n                                    volume based on the contents of the specified\n                                    data source. When the AnyVolumeDataSource feature\n                                    gate is enabled, dataSource contents will be copied\n                                    to dataSourceRef, and dataSourceRef contents will\n                                    be copied to dataSource when dataSourceRef.namespace\n                                    is not specified. If the namespace is specified,\n                                    then dataSourceRef will not be copied to dataSource.'\n                                  properties:\n                                    apiGroup:\n                                      description: APIGroup is the group for the resource\n                                        being referenced. If APIGroup is not specified,\n                                        the specified Kind must be in the core API\n                                        group. For any other third-party types, APIGroup\n                                        is required.\n                                      type: string\n                                    kind:\n                                      description: Kind is the type of resource being\n                                        referenced\n                                      type: string\n                                    name:\n                                      description: Name is the name of resource being\n                                        referenced\n                                      type: string\n                                  required:\n                                  - kind\n                                  - name\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                dataSourceRef:\n                                  description: 'dataSourceRef specifies the object\n                                    from which to populate the volume with data, if\n                                    a non-empty volume is desired. This may be any\n                                    object from a non-empty API group (non core object)\n                                    or a PersistentVolumeClaim object. When this field\n                                    is specified, volume binding will only succeed\n                                    if the type of the specified object matches some\n                                    installed volume populator or dynamic provisioner.\n                                    This field will replace the functionality of the\n                                    dataSource field and as such if both fields are\n                                    non-empty, they must have the same value. For\n                                    backwards compatibility, when namespace isn''t\n                                    specified in dataSourceRef, both fields (dataSource\n                                    and dataSourceRef) will be set to the same value\n                                    automatically if one of them is empty and the\n                                    other is non-empty. When namespace is specified\n                                    in dataSourceRef, dataSource isn''t set to the\n                                    same value and must be empty. There are three\n                                    important differences between dataSource and dataSourceRef:\n                                    * While dataSource only allows two specific types\n                                    of objects, dataSourceRef allows any non-core\n                                    object, as well as PersistentVolumeClaim objects.\n                                    * While dataSource ignores disallowed values (dropping\n                                    them), dataSourceRef preserves all values, and\n                                    generates an error if a disallowed value is specified.\n                                    * While dataSource only allows local objects,\n                                    dataSourceRef allows objects in any namespaces.\n                                    (Beta) Using this field requires the AnyVolumeDataSource\n                                    feature gate to be enabled. (Alpha) Using the\n                                    namespace field of dataSourceRef requires the\n                                    CrossNamespaceVolumeDataSource feature gate to\n                                    be enabled.'\n                                  properties:\n                                    apiGroup:\n                                      description: APIGroup is the group for the resource\n                                        being referenced. If APIGroup is not specified,\n                                        the specified Kind must be in the core API\n                                        group. For any other third-party types, APIGroup\n                                        is required.\n                                      type: string\n                                    kind:\n                                      description: Kind is the type of resource being\n                                        referenced\n                                      type: string\n                                    name:\n                                      description: Name is the name of resource being\n                                        referenced\n                                      type: string\n                                    namespace:\n                                      description: Namespace is the namespace of resource\n                                        being referenced Note that when a namespace\n                                        is specified, a gateway.networking.k8s.io/ReferenceGrant\n                                        object is required in the referent namespace\n                                        to allow that namespace's owner to accept\n                                        the reference. See the ReferenceGrant documentation\n                                        for details. (Alpha) This field requires the\n                                        CrossNamespaceVolumeDataSource feature gate\n                                        to be enabled.\n                                      type: string\n                                  required:\n                                  - kind\n                                  - name\n                                  type: object\n                                resources:\n                                  description: 'resources represents the minimum resources\n                                    the volume should have. If RecoverVolumeExpansionFailure\n                                    feature is enabled users are allowed to specify\n                                    resource requirements that are lower than previous\n                                    value but must still be higher than capacity recorded\n                                    in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                                  properties:\n                                    claims:\n                                      description: \"Claims lists the names of resources,\n                                        defined in spec.resourceClaims, that are used\n                                        by this container. \\n This is an alpha field\n                                        and requires enabling the DynamicResourceAllocation\n                                        feature gate. \\n This field is immutable.\n                                        It can only be set for containers.\"\n                                      items:\n                                        description: ResourceClaim references one\n                                          entry in PodSpec.ResourceClaims.\n                                        properties:\n                                          name:\n                                            description: Name must match the name\n                                              of one entry in pod.spec.resourceClaims\n                                              of the Pod where this field is used.\n                                              It makes that resource available inside\n                                              a container.\n                                            type: string\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    limits:\n                                      additionalProperties:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                        x-kubernetes-int-or-string: true\n                                      description: 'Limits describes the maximum amount\n                                        of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                      type: object\n                                    requests:\n                                      additionalProperties:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                        x-kubernetes-int-or-string: true\n                                      description: 'Requests describes the minimum\n                                        amount of compute resources required. If Requests\n                                        is omitted for a container, it defaults to\n                                        Limits if that is explicitly specified, otherwise\n                                        to an implementation-defined value. Requests\n                                        cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                      type: object\n                                  type: object\n                                selector:\n                                  description: selector is a label query over volumes\n                                    to consider for binding.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                storageClassName:\n                                  description: 'storageClassName is the name of the\n                                    StorageClass required by the claim. More info:\n                                    https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                                  type: string\n                                volumeMode:\n                                  description: volumeMode defines what type of volume\n                                    is required by the claim. Value of Filesystem\n                                    is implied when not included in claim spec.\n                                  type: string\n                                volumeName:\n                                  description: volumeName is the binding reference\n                                    to the PersistentVolume backing this claim.\n                                  type: string\n                              type: object\n                          required:\n                          - spec\n                          type: object\n                      type: object\n                    fc:\n                      description: fc represents a Fibre Channel resource that is\n                        attached to a kubelet's host machine and then exposed to the\n                        pod.\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. TODO: how do we prevent errors in the\n                            filesystem from compromising the machine'\n                          type: string\n                        lun:\n                          description: 'lun is Optional: FC target lun number'\n                          format: int32\n                          type: integer\n                        readOnly:\n                          description: 'readOnly is Optional: Defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.'\n                          type: boolean\n                        targetWWNs:\n                          description: 'targetWWNs is Optional: FC target worldwide\n                            names (WWNs)'\n                          items:\n                            type: string\n                          type: array\n                        wwids:\n                          description: 'wwids Optional: FC volume world wide identifiers\n                            (wwids) Either wwids or combination of targetWWNs and\n                            lun must be set, but not both simultaneously.'\n                          items:\n                            type: string\n                          type: array\n                      type: object\n                    flexVolume:\n                      description: flexVolume represents a generic volume resource\n                        that is provisioned/attached using an exec based plugin.\n                      properties:\n                        driver:\n                          description: driver is the name of the driver to use for\n                            this volume.\n                          type: string\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends\n                            on FlexVolume script.\n                          type: string\n                        options:\n                          additionalProperties:\n                            type: string\n                          description: 'options is Optional: this field holds extra\n                            command options if any.'\n                          type: object\n                        readOnly:\n                          description: 'readOnly is Optional: defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is Optional: secretRef is reference\n                            to the secret object containing sensitive information\n                            to pass to the plugin scripts. This may be empty if no\n                            secret object is specified. If the secret object contains\n                            more than one secret, all secrets are passed to the plugin\n                            scripts.'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      required:\n                      - driver\n                      type: object\n                    flocker:\n                      description: flocker represents a Flocker volume attached to\n                        a kubelet's host machine. This depends on the Flocker control\n                        service being running\n                      properties:\n                        datasetName:\n                          description: datasetName is Name of the dataset stored as\n                            metadata -> name on the dataset for Flocker should be\n                            considered as deprecated\n                          type: string\n                        datasetUUID:\n                          description: datasetUUID is the UUID of the dataset. This\n                            is unique identifier of a Flocker dataset\n                          type: string\n                      type: object\n                    gcePersistentDisk:\n                      description: 'gcePersistentDisk represents a GCE Disk resource\n                        that is attached to a kubelet''s host machine and then exposed\n                        to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                      properties:\n                        fsType:\n                          description: 'fsType is filesystem type of the volume that\n                            you want to mount. Tip: Ensure that the filesystem type\n                            is supported by the host operating system. Examples: \"ext4\",\n                            \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        partition:\n                          description: 'partition is the partition in the volume that\n                            you want to mount. If omitted, the default is to mount\n                            by volume name. Examples: For volume /dev/sda1, you specify\n                            the partition as \"1\". Similarly, the volume partition\n                            for /dev/sda is \"0\" (or you can leave the property empty).\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          format: int32\n                          type: integer\n                        pdName:\n                          description: 'pdName is unique name of the PD resource in\n                            GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          type: boolean\n                      required:\n                      - pdName\n                      type: object\n                    gitRepo:\n                      description: 'gitRepo represents a git repository at a particular\n                        revision. DEPRECATED: GitRepo is deprecated. To provision\n                        a container with a git repo, mount an EmptyDir into an InitContainer\n                        that clones the repo using git, then mount the EmptyDir into\n                        the Pod''s container.'\n                      properties:\n                        directory:\n                          description: directory is the target directory name. Must\n                            not contain or start with '..'.  If '.' is supplied, the\n                            volume directory will be the git repository.  Otherwise,\n                            if specified, the volume will contain the git repository\n                            in the subdirectory with the given name.\n                          type: string\n                        repository:\n                          description: repository is the URL\n                          type: string\n                        revision:\n                          description: revision is the commit hash for the specified\n                            revision.\n                          type: string\n                      required:\n                      - repository\n                      type: object\n                    glusterfs:\n                      description: 'glusterfs represents a Glusterfs mount on the\n                        host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md'\n                      properties:\n                        endpoints:\n                          description: 'endpoints is the endpoint name that details\n                            Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: string\n                        path:\n                          description: 'path is the Glusterfs volume path. More info:\n                            https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the Glusterfs volume\n                            to be mounted with read-only permissions. Defaults to\n                            false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: boolean\n                      required:\n                      - endpoints\n                      - path\n                      type: object\n                    hostPath:\n                      description: 'hostPath represents a pre-existing file or directory\n                        on the host machine that is directly exposed to the container.\n                        This is generally used for system agents or other privileged\n                        things that are allowed to see the host machine. Most containers\n                        will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n                        --- TODO(jonesdl) We need to restrict who can use host directory\n                        mounts and who can/can not mount host directories as read/write.'\n                      properties:\n                        path:\n                          description: 'path of the directory on the host. If the\n                            path is a symlink, it will follow the link to the real\n                            path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                          type: string\n                        type:\n                          description: 'type for HostPath Volume Defaults to \"\" More\n                            info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                          type: string\n                      required:\n                      - path\n                      type: object\n                    iscsi:\n                      description: 'iscsi represents an ISCSI Disk resource that is\n                        attached to a kubelet''s host machine and then exposed to\n                        the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md'\n                      properties:\n                        chapAuthDiscovery:\n                          description: chapAuthDiscovery defines whether support iSCSI\n                            Discovery CHAP authentication\n                          type: boolean\n                        chapAuthSession:\n                          description: chapAuthSession defines whether support iSCSI\n                            Session CHAP authentication\n                          type: boolean\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        initiatorName:\n                          description: initiatorName is the custom iSCSI Initiator\n                            Name. If initiatorName is specified with iscsiInterface\n                            simultaneously, new iSCSI interface <target portal>:<volume\n                            name> will be created for the connection.\n                          type: string\n                        iqn:\n                          description: iqn is the target iSCSI Qualified Name.\n                          type: string\n                        iscsiInterface:\n                          description: iscsiInterface is the interface Name that uses\n                            an iSCSI transport. Defaults to 'default' (tcp).\n                          type: string\n                        lun:\n                          description: lun represents iSCSI Target Lun number.\n                          format: int32\n                          type: integer\n                        portals:\n                          description: portals is the iSCSI Target Portal List. The\n                            portal is either an IP or ip_addr:port if the port is\n                            other than default (typically TCP ports 860 and 3260).\n                          items:\n                            type: string\n                          type: array\n                        readOnly:\n                          description: readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false.\n                          type: boolean\n                        secretRef:\n                          description: secretRef is the CHAP Secret for iSCSI target\n                            and initiator authentication\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        targetPortal:\n                          description: targetPortal is iSCSI Target Portal. The Portal\n                            is either an IP or ip_addr:port if the port is other than\n                            default (typically TCP ports 860 and 3260).\n                          type: string\n                      required:\n                      - iqn\n                      - lun\n                      - targetPortal\n                      type: object\n                    name:\n                      description: 'name of the volume. Must be a DNS_LABEL and unique\n                        within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'\n                      type: string\n                    nfs:\n                      description: 'nfs represents an NFS mount on the host that shares\n                        a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                      properties:\n                        path:\n                          description: 'path that is exported by the NFS server. More\n                            info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the NFS export to\n                            be mounted with read-only permissions. Defaults to false.\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: boolean\n                        server:\n                          description: 'server is the hostname or IP address of the\n                            NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: string\n                      required:\n                      - path\n                      - server\n                      type: object\n                    persistentVolumeClaim:\n                      description: 'persistentVolumeClaimVolumeSource represents a\n                        reference to a PersistentVolumeClaim in the same namespace.\n                        More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                      properties:\n                        claimName:\n                          description: 'claimName is the name of a PersistentVolumeClaim\n                            in the same namespace as the pod using this volume. More\n                            info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                          type: string\n                        readOnly:\n                          description: readOnly Will force the ReadOnly setting in\n                            VolumeMounts. Default false.\n                          type: boolean\n                      required:\n                      - claimName\n                      type: object\n                    photonPersistentDisk:\n                      description: photonPersistentDisk represents a PhotonController\n                        persistent disk attached and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        pdID:\n                          description: pdID is the ID that identifies Photon Controller\n                            persistent disk\n                          type: string\n                      required:\n                      - pdID\n                      type: object\n                    portworxVolume:\n                      description: portworxVolume represents a portworx volume attached\n                        and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fSType represents the filesystem type to mount\n                            Must be a filesystem type supported by the host operating\n                            system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        volumeID:\n                          description: volumeID uniquely identifies a Portworx volume\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    projected:\n                      description: projected items for all in one resources secrets,\n                        configmaps, and downward API\n                      properties:\n                        defaultMode:\n                          description: defaultMode are the mode bits used to set permissions\n                            on created files by default. Must be an octal value between\n                            0000 and 0777 or a decimal value between 0 and 511. YAML\n                            accepts both octal and decimal values, JSON requires decimal\n                            values for mode bits. Directories within the path are\n                            not affected by this setting. This might be in conflict\n                            with other options that affect the file mode, like fsGroup,\n                            and the result can be other mode bits set.\n                          format: int32\n                          type: integer\n                        sources:\n                          description: sources is the list of volume projections\n                          items:\n                            description: Projection that may be projected along with\n                              other supported volume types\n                            properties:\n                              configMap:\n                                description: configMap information about the configMap\n                                  data to project\n                                properties:\n                                  items:\n                                    description: items if unspecified, each key-value\n                                      pair in the Data field of the referenced ConfigMap\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the ConfigMap, the volume\n                                      setup will error unless it is marked optional.\n                                      Paths must be relative and may not contain the\n                                      '..' path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: key is the key to project.\n                                          type: string\n                                        mode:\n                                          description: 'mode is Optional: mode bits\n                                            used to set permissions on this file.\n                                            Must be an octal value between 0000 and\n                                            0777 or a decimal value between 0 and\n                                            511. YAML accepts both octal and decimal\n                                            values, JSON requires decimal values for\n                                            mode bits. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: path is the relative path of\n                                            the file to map the key to. May not be\n                                            an absolute path. May not contain the\n                                            path element '..'. May not start with\n                                            the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: optional specify whether the ConfigMap\n                                      or its keys must be defined\n                                    type: boolean\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              downwardAPI:\n                                description: downwardAPI information about the downwardAPI\n                                  data to project\n                                properties:\n                                  items:\n                                    description: Items is a list of DownwardAPIVolume\n                                      file\n                                    items:\n                                      description: DownwardAPIVolumeFile represents\n                                        information to create the file containing\n                                        the pod field\n                                      properties:\n                                        fieldRef:\n                                          description: 'Required: Selects a field\n                                            of the pod: only annotations, labels,\n                                            name and namespace are supported.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        mode:\n                                          description: 'Optional: mode bits used to\n                                            set permissions on this file, must be\n                                            an octal value between 0000 and 0777 or\n                                            a decimal value between 0 and 511. YAML\n                                            accepts both octal and decimal values,\n                                            JSON requires decimal values for mode\n                                            bits. If not specified, the volume defaultMode\n                                            will be used. This might be in conflict\n                                            with other options that affect the file\n                                            mode, like fsGroup, and the result can\n                                            be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: 'Required: Path is  the relative\n                                            path name of the file to be created. Must\n                                            not be absolute or contain the ''..''\n                                            path. Must be utf-8 encoded. The first\n                                            item of the relative path must not start\n                                            with ''..'''\n                                          type: string\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, requests.cpu\n                                            and requests.memory) are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - path\n                                      type: object\n                                    type: array\n                                type: object\n                              secret:\n                                description: secret information about the secret data\n                                  to project\n                                properties:\n                                  items:\n                                    description: items if unspecified, each key-value\n                                      pair in the Data field of the referenced Secret\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the Secret, the volume setup\n                                      will error unless it is marked optional. Paths\n                                      must be relative and may not contain the '..'\n                                      path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: key is the key to project.\n                                          type: string\n                                        mode:\n                                          description: 'mode is Optional: mode bits\n                                            used to set permissions on this file.\n                                            Must be an octal value between 0000 and\n                                            0777 or a decimal value between 0 and\n                                            511. YAML accepts both octal and decimal\n                                            values, JSON requires decimal values for\n                                            mode bits. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: path is the relative path of\n                                            the file to map the key to. May not be\n                                            an absolute path. May not contain the\n                                            path element '..'. May not start with\n                                            the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: optional field specify whether the\n                                      Secret or its key must be defined\n                                    type: boolean\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              serviceAccountToken:\n                                description: serviceAccountToken is information about\n                                  the serviceAccountToken data to project\n                                properties:\n                                  audience:\n                                    description: audience is the intended audience\n                                      of the token. A recipient of a token must identify\n                                      itself with an identifier specified in the audience\n                                      of the token, and otherwise should reject the\n                                      token. The audience defaults to the identifier\n                                      of the apiserver.\n                                    type: string\n                                  expirationSeconds:\n                                    description: expirationSeconds is the requested\n                                      duration of validity of the service account\n                                      token. As the token approaches expiration, the\n                                      kubelet volume plugin will proactively rotate\n                                      the service account token. The kubelet will\n                                      start trying to rotate the token if the token\n                                      is older than 80 percent of its time to live\n                                      or if the token is older than 24 hours.Defaults\n                                      to 1 hour and must be at least 10 minutes.\n                                    format: int64\n                                    type: integer\n                                  path:\n                                    description: path is the path relative to the\n                                      mount point of the file to project the token\n                                      into.\n                                    type: string\n                                required:\n                                - path\n                                type: object\n                            type: object\n                          type: array\n                      type: object\n                    quobyte:\n                      description: quobyte represents a Quobyte mount on the host\n                        that shares a pod's lifetime\n                      properties:\n                        group:\n                          description: group to map volume access to Default is no\n                            group\n                          type: string\n                        readOnly:\n                          description: readOnly here will force the Quobyte volume\n                            to be mounted with read-only permissions. Defaults to\n                            false.\n                          type: boolean\n                        registry:\n                          description: registry represents a single or multiple Quobyte\n                            Registry services specified as a string as host:port pair\n                            (multiple entries are separated with commas) which acts\n                            as the central registry for volumes\n                          type: string\n                        tenant:\n                          description: tenant owning the given Quobyte volume in the\n                            Backend Used with dynamically provisioned Quobyte volumes,\n                            value is set by the plugin\n                          type: string\n                        user:\n                          description: user to map volume access to Defaults to serivceaccount\n                            user\n                          type: string\n                        volume:\n                          description: volume is a string that references an already\n                            created Quobyte volume by name.\n                          type: string\n                      required:\n                      - registry\n                      - volume\n                      type: object\n                    rbd:\n                      description: 'rbd represents a Rados Block Device mount on the\n                        host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        image:\n                          description: 'image is the rados image name. More info:\n                            https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        keyring:\n                          description: 'keyring is the path to key ring for RBDUser.\n                            Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        monitors:\n                          description: 'monitors is a collection of Ceph monitors.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          items:\n                            type: string\n                          type: array\n                        pool:\n                          description: 'pool is the rados pool name. Default is rbd.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is name of the authentication secret\n                            for RBDUser. If provided overrides keyring. Default is\n                            nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        user:\n                          description: 'user is the rados user name. Default is admin.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                      required:\n                      - image\n                      - monitors\n                      type: object\n                    scaleIO:\n                      description: scaleIO represents a ScaleIO persistent volume\n                        attached and mounted on Kubernetes nodes.\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\".\n                          type: string\n                        gateway:\n                          description: gateway is the host address of the ScaleIO\n                            API Gateway.\n                          type: string\n                        protectionDomain:\n                          description: protectionDomain is the name of the ScaleIO\n                            Protection Domain for the configured storage.\n                          type: string\n                        readOnly:\n                          description: readOnly Defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretRef:\n                          description: secretRef references to the secret for ScaleIO\n                            user and other sensitive information. If this is not provided,\n                            Login operation will fail.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        sslEnabled:\n                          description: sslEnabled Flag enable/disable SSL communication\n                            with Gateway, default false\n                          type: boolean\n                        storageMode:\n                          description: storageMode indicates whether the storage for\n                            a volume should be ThickProvisioned or ThinProvisioned.\n                            Default is ThinProvisioned.\n                          type: string\n                        storagePool:\n                          description: storagePool is the ScaleIO Storage Pool associated\n                            with the protection domain.\n                          type: string\n                        system:\n                          description: system is the name of the storage system as\n                            configured in ScaleIO.\n                          type: string\n                        volumeName:\n                          description: volumeName is the name of a volume already\n                            created in the ScaleIO system that is associated with\n                            this volume source.\n                          type: string\n                      required:\n                      - gateway\n                      - secretRef\n                      - system\n                      type: object\n                    secret:\n                      description: 'secret represents a secret that should populate\n                        this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                      properties:\n                        defaultMode:\n                          description: 'defaultMode is Optional: mode bits used to\n                            set permissions on created files by default. Must be an\n                            octal value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: items If unspecified, each key-value pair in\n                            the Data field of the referenced Secret will be projected\n                            into the volume as a file whose name is the key and content\n                            is the value. If specified, the listed keys will be projected\n                            into the specified paths, and unlisted keys will not be\n                            present. If a key is specified which is not present in\n                            the Secret, the volume setup will error unless it is marked\n                            optional. Paths must be relative and may not contain the\n                            '..' path or start with '..'.\n                          items:\n                            description: Maps a string key to a path within a volume.\n                            properties:\n                              key:\n                                description: key is the key to project.\n                                type: string\n                              mode:\n                                description: 'mode is Optional: mode bits used to\n                                  set permissions on this file. Must be an octal value\n                                  between 0000 and 0777 or a decimal value between\n                                  0 and 511. YAML accepts both octal and decimal values,\n                                  JSON requires decimal values for mode bits. If not\n                                  specified, the volume defaultMode will be used.\n                                  This might be in conflict with other options that\n                                  affect the file mode, like fsGroup, and the result\n                                  can be other mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: path is the relative path of the file\n                                  to map the key to. May not be an absolute path.\n                                  May not contain the path element '..'. May not start\n                                  with the string '..'.\n                                type: string\n                            required:\n                            - key\n                            - path\n                            type: object\n                          type: array\n                        optional:\n                          description: optional field specify whether the Secret or\n                            its keys must be defined\n                          type: boolean\n                        secretName:\n                          description: 'secretName is the name of the secret in the\n                            pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                          type: string\n                      type: object\n                    storageos:\n                      description: storageOS represents a StorageOS volume attached\n                        and mounted on Kubernetes nodes.\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretRef:\n                          description: secretRef specifies the secret to use for obtaining\n                            the StorageOS API credentials.  If not specified, default\n                            values will be attempted.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        volumeName:\n                          description: volumeName is the human-readable name of the\n                            StorageOS volume.  Volume names are only unique within\n                            a namespace.\n                          type: string\n                        volumeNamespace:\n                          description: volumeNamespace specifies the scope of the\n                            volume within StorageOS.  If no namespace is specified\n                            then the Pod's namespace will be used.  This allows the\n                            Kubernetes name scoping to be mirrored within StorageOS\n                            for tighter integration. Set VolumeName to any name to\n                            override the default behaviour. Set to \"default\" if you\n                            are not using namespaces within StorageOS. Namespaces\n                            that do not pre-exist within StorageOS will be created.\n                          type: string\n                      type: object\n                    vsphereVolume:\n                      description: vsphereVolume represents a vSphere volume attached\n                        and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fsType is filesystem type to mount. Must be\n                            a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        storagePolicyID:\n                          description: storagePolicyID is the storage Policy Based\n                            Management (SPBM) profile ID associated with the StoragePolicyName.\n                          type: string\n                        storagePolicyName:\n                          description: storagePolicyName is the storage Policy Based\n                            Management (SPBM) profile name.\n                          type: string\n                        volumePath:\n                          description: volumePath is the path that identifies vSphere\n                            volume vmdk\n                          type: string\n                      required:\n                      - volumePath\n                      type: object\n                  required:\n                  - name\n                  type: object\n                type: array\n              walCompression:\n                description: \"Configures compression of the write-ahead log (WAL)\n                  using Snappy. \\n WAL compression is enabled by default for Prometheus\n                  >= 2.20.0 \\n Requires Prometheus v2.11.0 and above.\"\n                type: boolean\n              web:\n                description: Defines the configuration of the Prometheus web server.\n                properties:\n                  httpConfig:\n                    description: Defines HTTP parameters for web server.\n                    properties:\n                      headers:\n                        description: List of headers that can be added to HTTP responses.\n                        properties:\n                          contentSecurityPolicy:\n                            description: Set the Content-Security-Policy header to\n                              HTTP responses. Unset if blank.\n                            type: string\n                          strictTransportSecurity:\n                            description: Set the Strict-Transport-Security header\n                              to HTTP responses. Unset if blank. Please make sure\n                              that you use this with care as this header might force\n                              browsers to load Prometheus and the other applications\n                              hosted on the same domain and subdomains over HTTPS.\n                              https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security\n                            type: string\n                          xContentTypeOptions:\n                            description: Set the X-Content-Type-Options header to\n                              HTTP responses. Unset if blank. Accepted value is nosniff.\n                              https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options\n                            enum:\n                            - \"\"\n                            - NoSniff\n                            type: string\n                          xFrameOptions:\n                            description: Set the X-Frame-Options header to HTTP responses.\n                              Unset if blank. Accepted values are deny and sameorigin.\n                              https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options\n                            enum:\n                            - \"\"\n                            - Deny\n                            - SameOrigin\n                            type: string\n                          xXSSProtection:\n                            description: Set the X-XSS-Protection header to all responses.\n                              Unset if blank. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection\n                            type: string\n                        type: object\n                      http2:\n                        description: Enable HTTP/2 support. Note that HTTP/2 is only\n                          supported with TLS. When TLSConfig is not configured, HTTP/2\n                          will be disabled. Whenever the value of the field changes,\n                          a rolling update will be triggered.\n                        type: boolean\n                    type: object\n                  maxConnections:\n                    description: Defines the maximum number of simultaneous connections\n                      A zero value means that Prometheus doesn't accept any incoming\n                      connection.\n                    format: int32\n                    minimum: 0\n                    type: integer\n                  pageTitle:\n                    description: The prometheus web page title.\n                    type: string\n                  tlsConfig:\n                    description: Defines the TLS parameters for HTTPS.\n                    properties:\n                      cert:\n                        description: Contains the TLS certificate for the server.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      cipherSuites:\n                        description: 'List of supported cipher suites for TLS versions\n                          up to TLS 1.2. If empty, Go default cipher suites are used.\n                          Available cipher suites are documented in the go documentation:\n                          https://golang.org/pkg/crypto/tls/#pkg-constants'\n                        items:\n                          type: string\n                        type: array\n                      client_ca:\n                        description: Contains the CA certificate for client certificate\n                          authentication to the server.\n                        properties:\n                          configMap:\n                            description: ConfigMap containing data to use for the\n                              targets.\n                            properties:\n                              key:\n                                description: The key to select.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap or its\n                                  key must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          secret:\n                            description: Secret containing data to use for the targets.\n                            properties:\n                              key:\n                                description: The key of the secret to select from.  Must\n                                  be a valid secret key.\n                                type: string\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret or its key\n                                  must be defined\n                                type: boolean\n                            required:\n                            - key\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      clientAuthType:\n                        description: 'Server policy for client authentication. Maps\n                          to ClientAuth Policies. For more detail on clientAuth options:\n                          https://golang.org/pkg/crypto/tls/#ClientAuthType'\n                        type: string\n                      curvePreferences:\n                        description: 'Elliptic curves that will be used in an ECDHE\n                          handshake, in preference order. Available curves are documented\n                          in the go documentation: https://golang.org/pkg/crypto/tls/#CurveID'\n                        items:\n                          type: string\n                        type: array\n                      keySecret:\n                        description: Secret containing the TLS key for the server.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      maxVersion:\n                        description: Maximum TLS version that is acceptable. Defaults\n                          to TLS13.\n                        type: string\n                      minVersion:\n                        description: Minimum TLS version that is acceptable. Defaults\n                          to TLS12.\n                        type: string\n                      preferServerCipherSuites:\n                        description: Controls whether the server selects the client's\n                          most preferred cipher suite, or the server's most preferred\n                          cipher suite. If true then the server's preference, as expressed\n                          in the order of elements in cipherSuites, is used.\n                        type: boolean\n                    required:\n                    - cert\n                    - keySecret\n                    type: object\n                type: object\n            type: object\n          status:\n            description: 'Most recent observed status of the Prometheus cluster. Read-only.\n              More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n            properties:\n              availableReplicas:\n                description: Total number of available pods (ready for at least minReadySeconds)\n                  targeted by this Prometheus deployment.\n                format: int32\n                type: integer\n              conditions:\n                description: The current state of the Prometheus deployment.\n                items:\n                  description: Condition represents the state of the resources associated\n                    with the Prometheus, Alertmanager or ThanosRuler resource.\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the time of the last update\n                        to the current status property.\n                      format: date-time\n                      type: string\n                    message:\n                      description: Human-readable message indicating details for the\n                        condition's last transition.\n                      type: string\n                    observedGeneration:\n                      description: ObservedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if `.metadata.generation`\n                        is currently 12, but the `.status.conditions[].observedGeneration`\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      type: integer\n                    reason:\n                      description: Reason for the condition's last transition.\n                      type: string\n                    status:\n                      description: Status of the condition.\n                      type: string\n                    type:\n                      description: Type of the condition being reported.\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              paused:\n                description: Represents whether any actions on the underlying managed\n                  objects are being performed. Only delete actions will be performed.\n                type: boolean\n              replicas:\n                description: Total number of non-terminated pods targeted by this\n                  Prometheus deployment (their labels match the selector).\n                format: int32\n                type: integer\n              selector:\n                description: The selector used to match the pods targeted by this\n                  Prometheus resource.\n                type: string\n              shardStatuses:\n                description: The list has one entry per shard. Each entry provides\n                  a summary of the shard status.\n                items:\n                  properties:\n                    availableReplicas:\n                      description: Total number of available pods (ready for at least\n                        minReadySeconds) targeted by this shard.\n                      format: int32\n                      type: integer\n                    replicas:\n                      description: Total number of pods targeted by this shard.\n                      format: int32\n                      type: integer\n                    shardID:\n                      description: Identifier of the shard.\n                      type: string\n                    unavailableReplicas:\n                      description: Total number of unavailable pods targeted by this\n                        shard.\n                      format: int32\n                      type: integer\n                    updatedReplicas:\n                      description: Total number of non-terminated pods targeted by\n                        this shard that have the desired spec.\n                      format: int32\n                      type: integer\n                  required:\n                  - availableReplicas\n                  - replicas\n                  - shardID\n                  - unavailableReplicas\n                  - updatedReplicas\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - shardID\n                x-kubernetes-list-type: map\n              shards:\n                description: Shards is the most recently observed number of shards.\n                format: int32\n                type: integer\n              unavailableReplicas:\n                description: Total number of unavailable pods targeted by this Prometheus\n                  deployment.\n                format: int32\n                type: integer\n              updatedReplicas:\n                description: Total number of non-terminated pods targeted by this\n                  Prometheus deployment that have the desired version spec.\n                format: int32\n                type: integer\n            required:\n            - availableReplicas\n            - paused\n            - replicas\n            - unavailableReplicas\n            - updatedReplicas\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      scale:\n        labelSelectorPath: .status.selector\n        specReplicasPath: .spec.shards\n        statusReplicasPath: .status.shards\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: prometheusrules.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: PrometheusRule\n    listKind: PrometheusRuleList\n    plural: prometheusrules\n    shortNames:\n    - promrule\n    singular: prometheusrule\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: PrometheusRule defines recording and alerting rules for a Prometheus\n          instance\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Specification of desired alerting rule definitions for Prometheus.\n            properties:\n              groups:\n                description: Content of Prometheus rule file\n                items:\n                  description: RuleGroup is a list of sequentially evaluated recording\n                    and alerting rules.\n                  properties:\n                    interval:\n                      description: Interval determines how often rules in the group\n                        are evaluated.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    limit:\n                      description: Limit the number of alerts an alerting rule and\n                        series a recording rule can produce. Limit is supported starting\n                        with Prometheus >= 2.31 and Thanos Ruler >= 0.24.\n                      type: integer\n                    name:\n                      description: Name of the rule group.\n                      minLength: 1\n                      type: string\n                    partial_response_strategy:\n                      description: 'PartialResponseStrategy is only used by ThanosRuler\n                        and will be ignored by Prometheus instances. More info: https://github.com/thanos-io/thanos/blob/main/docs/components/rule.md#partial-response'\n                      pattern: ^(?i)(abort|warn)?$\n                      type: string\n                    rules:\n                      description: List of alerting and recording rules.\n                      items:\n                        description: 'Rule describes an alerting or recording rule\n                          See Prometheus documentation: [alerting](https://www.prometheus.io/docs/prometheus/latest/configuration/alerting_rules/)\n                          or [recording](https://www.prometheus.io/docs/prometheus/latest/configuration/recording_rules/#recording-rules)\n                          rule'\n                        properties:\n                          alert:\n                            description: Name of the alert. Must be a valid label\n                              value. Only one of `record` and `alert` must be set.\n                            type: string\n                          annotations:\n                            additionalProperties:\n                              type: string\n                            description: Annotations to add to each alert. Only valid\n                              for alerting rules.\n                            type: object\n                          expr:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            description: PromQL expression to evaluate.\n                            x-kubernetes-int-or-string: true\n                          for:\n                            description: Alerts are considered firing once they have\n                              been returned for this long.\n                            pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                            type: string\n                          keep_firing_for:\n                            description: KeepFiringFor defines how long an alert will\n                              continue firing after the condition that triggered it\n                              has cleared.\n                            minLength: 1\n                            pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                            type: string\n                          labels:\n                            additionalProperties:\n                              type: string\n                            description: Labels to add or overwrite.\n                            type: object\n                          record:\n                            description: Name of the time series to output to. Must\n                              be a valid metric name. Only one of `record` and `alert`\n                              must be set.\n                            type: string\n                        required:\n                        - expr\n                        type: object\n                      type: array\n                  required:\n                  - name\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - name\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: podmonitors.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: PodMonitor\n    listKind: PodMonitorList\n    plural: podmonitors\n    shortNames:\n    - pmon\n    singular: podmonitor\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: PodMonitor defines monitoring for a set of pods.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Specification of desired Pod selection for target discovery\n              by Prometheus.\n            properties:\n              attachMetadata:\n                description: \"`attachMetadata` defines additional metadata which is\n                  added to the discovered targets. \\n It requires Prometheus >= v2.37.0.\"\n                properties:\n                  node:\n                    description: When set to true, Prometheus must have the `get`\n                      permission on the `Nodes` objects.\n                    type: boolean\n                type: object\n              jobLabel:\n                description: \"The label to use to retrieve the job name from. `jobLabel`\n                  selects the label from the associated Kubernetes `Pod` object which\n                  will be used as the `job` label for all metrics. \\n For example\n                  if `jobLabel` is set to `foo` and the Kubernetes `Pod` object is\n                  labeled with `foo: bar`, then Prometheus adds the `job=\\\"bar\\\"`\n                  label to all ingested metrics. \\n If the value of this field is\n                  empty, the `job` label of the metrics defaults to the namespace\n                  and name of the PodMonitor object (e.g. `<namespace>/<name>`).\"\n                type: string\n              keepDroppedTargets:\n                description: \"Per-scrape limit on the number of targets dropped by\n                  relabeling that will be kept in memory. 0 means no limit. \\n It\n                  requires Prometheus >= v2.47.0.\"\n                format: int64\n                type: integer\n              labelLimit:\n                description: \"Per-scrape limit on number of labels that will be accepted\n                  for a sample. \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              labelNameLengthLimit:\n                description: \"Per-scrape limit on length of labels name that will\n                  be accepted for a sample. \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              labelValueLengthLimit:\n                description: \"Per-scrape limit on length of labels value that will\n                  be accepted for a sample. \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              namespaceSelector:\n                description: Selector to select which namespaces the Kubernetes `Pods`\n                  objects are discovered from.\n                properties:\n                  any:\n                    description: Boolean describing whether all namespaces are selected\n                      in contrast to a list restricting them.\n                    type: boolean\n                  matchNames:\n                    description: List of namespace names to select from.\n                    items:\n                      type: string\n                    type: array\n                type: object\n              podMetricsEndpoints:\n                description: List of endpoints part of this PodMonitor.\n                items:\n                  description: PodMetricsEndpoint defines an endpoint serving Prometheus\n                    metrics to be scraped by Prometheus.\n                  properties:\n                    authorization:\n                      description: \"`authorization` configures the Authorization header\n                        credentials to use when scraping the target. \\n Cannot be\n                        set at the same time as `basicAuth`, or `oauth2`.\"\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    basicAuth:\n                      description: \"`basicAuth` configures the Basic Authentication\n                        credentials to use when scraping the target. \\n Cannot be\n                        set at the same time as `authorization`, or `oauth2`.\"\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    bearerTokenSecret:\n                      description: \"`bearerTokenSecret` specifies a key of a Secret\n                        containing the bearer token for scraping targets. The secret\n                        needs to be in the same namespace as the PodMonitor object\n                        and readable by the Prometheus Operator. \\n Deprecated: use\n                        `authorization` instead.\"\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    enableHttp2:\n                      description: '`enableHttp2` can be used to disable HTTP2 when\n                        scraping the target.'\n                      type: boolean\n                    filterRunning:\n                      description: \"When true, the pods which are not running (e.g.\n                        either in Failed or Succeeded state) are dropped during the\n                        target discovery. \\n If unset, the filtering is enabled. \\n\n                        More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase\"\n                      type: boolean\n                    followRedirects:\n                      description: '`followRedirects` defines whether the scrape requests\n                        should follow HTTP 3xx redirects.'\n                      type: boolean\n                    honorLabels:\n                      description: When true, `honorLabels` preserves the metric's\n                        labels when they collide with the target's labels.\n                      type: boolean\n                    honorTimestamps:\n                      description: '`honorTimestamps` controls whether Prometheus\n                        preserves the timestamps when exposed by the target.'\n                      type: boolean\n                    interval:\n                      description: \"Interval at which Prometheus scrapes the metrics\n                        from the target. \\n If empty, Prometheus uses the global scrape\n                        interval.\"\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    metricRelabelings:\n                      description: '`metricRelabelings` configures the relabeling\n                        rules to apply to the samples before ingestion.'\n                      items:\n                        description: \"RelabelConfig allows dynamic rewriting of the\n                          label set for targets, alerts, scraped samples and remote\n                          write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                        properties:\n                          action:\n                            default: replace\n                            description: \"Action to perform based on the regex matching.\n                              \\n `Uppercase` and `Lowercase` actions require Prometheus\n                              >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                              Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                            enum:\n                            - replace\n                            - Replace\n                            - keep\n                            - Keep\n                            - drop\n                            - Drop\n                            - hashmod\n                            - HashMod\n                            - labelmap\n                            - LabelMap\n                            - labeldrop\n                            - LabelDrop\n                            - labelkeep\n                            - LabelKeep\n                            - lowercase\n                            - Lowercase\n                            - uppercase\n                            - Uppercase\n                            - keepequal\n                            - KeepEqual\n                            - dropequal\n                            - DropEqual\n                            type: string\n                          modulus:\n                            description: \"Modulus to take of the hash of the source\n                              label values. \\n Only applicable when the action is\n                              `HashMod`.\"\n                            format: int64\n                            type: integer\n                          regex:\n                            description: Regular expression against which the extracted\n                              value is matched.\n                            type: string\n                          replacement:\n                            description: \"Replacement value against which a Replace\n                              action is performed if the regular expression matches.\n                              \\n Regex capture groups are available.\"\n                            type: string\n                          separator:\n                            description: Separator is the string between concatenated\n                              SourceLabels.\n                            type: string\n                          sourceLabels:\n                            description: The source labels select values from existing\n                              labels. Their content is concatenated using the configured\n                              Separator and matched against the configured regular\n                              expression.\n                            items:\n                              description: LabelName is a valid Prometheus label name\n                                which may only contain ASCII letters, numbers, as\n                                well as underscores.\n                              pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                              type: string\n                            type: array\n                          targetLabel:\n                            description: \"Label to which the resulting string is written\n                              in a replacement. \\n It is mandatory for `Replace`,\n                              `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                              `DropEqual` actions. \\n Regex capture groups are available.\"\n                            type: string\n                        type: object\n                      type: array\n                    oauth2:\n                      description: \"`oauth2` configures the OAuth2 settings to use\n                        when scraping the target. \\n It requires Prometheus >= 2.27.0.\n                        \\n Cannot be set at the same time as `authorization`, or `basicAuth`.\"\n                      properties:\n                        clientId:\n                          description: '`clientId` specifies a key of a Secret or\n                            ConfigMap containing the OAuth2 client''s ID.'\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        clientSecret:\n                          description: '`clientSecret` specifies a key of a Secret\n                            containing the OAuth2 client''s secret.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        endpointParams:\n                          additionalProperties:\n                            type: string\n                          description: '`endpointParams` configures the HTTP parameters\n                            to append to the token URL.'\n                          type: object\n                        scopes:\n                          description: '`scopes` defines the OAuth2 scopes used for\n                            the token request.'\n                          items:\n                            type: string\n                          type: array\n                        tokenUrl:\n                          description: '`tokenURL` configures the URL to fetch the\n                            token from.'\n                          minLength: 1\n                          type: string\n                      required:\n                      - clientId\n                      - clientSecret\n                      - tokenUrl\n                      type: object\n                    params:\n                      additionalProperties:\n                        items:\n                          type: string\n                        type: array\n                      description: '`params` define optional HTTP URL parameters.'\n                      type: object\n                    path:\n                      description: \"HTTP path from which to scrape for metrics. \\n\n                        If empty, Prometheus uses the default value (e.g. `/metrics`).\"\n                      type: string\n                    port:\n                      description: \"Name of the Pod port which this endpoint refers\n                        to. \\n It takes precedence over `targetPort`.\"\n                      type: string\n                    proxyUrl:\n                      description: '`proxyURL` configures the HTTP Proxy URL (e.g.\n                        \"http://proxyserver:2195\") to go through when scraping the\n                        target.'\n                      type: string\n                    relabelings:\n                      description: \"`relabelings` configures the relabeling rules\n                        to apply the target's metadata labels. \\n The Operator automatically\n                        adds relabelings for a few standard Kubernetes fields. \\n\n                        The original scrape job's name is available via the `__tmp_prometheus_job_name`\n                        label. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                      items:\n                        description: \"RelabelConfig allows dynamic rewriting of the\n                          label set for targets, alerts, scraped samples and remote\n                          write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                        properties:\n                          action:\n                            default: replace\n                            description: \"Action to perform based on the regex matching.\n                              \\n `Uppercase` and `Lowercase` actions require Prometheus\n                              >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                              Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                            enum:\n                            - replace\n                            - Replace\n                            - keep\n                            - Keep\n                            - drop\n                            - Drop\n                            - hashmod\n                            - HashMod\n                            - labelmap\n                            - LabelMap\n                            - labeldrop\n                            - LabelDrop\n                            - labelkeep\n                            - LabelKeep\n                            - lowercase\n                            - Lowercase\n                            - uppercase\n                            - Uppercase\n                            - keepequal\n                            - KeepEqual\n                            - dropequal\n                            - DropEqual\n                            type: string\n                          modulus:\n                            description: \"Modulus to take of the hash of the source\n                              label values. \\n Only applicable when the action is\n                              `HashMod`.\"\n                            format: int64\n                            type: integer\n                          regex:\n                            description: Regular expression against which the extracted\n                              value is matched.\n                            type: string\n                          replacement:\n                            description: \"Replacement value against which a Replace\n                              action is performed if the regular expression matches.\n                              \\n Regex capture groups are available.\"\n                            type: string\n                          separator:\n                            description: Separator is the string between concatenated\n                              SourceLabels.\n                            type: string\n                          sourceLabels:\n                            description: The source labels select values from existing\n                              labels. Their content is concatenated using the configured\n                              Separator and matched against the configured regular\n                              expression.\n                            items:\n                              description: LabelName is a valid Prometheus label name\n                                which may only contain ASCII letters, numbers, as\n                                well as underscores.\n                              pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                              type: string\n                            type: array\n                          targetLabel:\n                            description: \"Label to which the resulting string is written\n                              in a replacement. \\n It is mandatory for `Replace`,\n                              `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                              `DropEqual` actions. \\n Regex capture groups are available.\"\n                            type: string\n                        type: object\n                      type: array\n                    scheme:\n                      description: \"HTTP scheme to use for scraping. \\n `http` and\n                        `https` are the expected values unless you rewrite the `__scheme__`\n                        label via relabeling. \\n If empty, Prometheus uses the default\n                        value `http`.\"\n                      enum:\n                      - http\n                      - https\n                      type: string\n                    scrapeTimeout:\n                      description: \"Timeout after which Prometheus considers the scrape\n                        to be failed. \\n If empty, Prometheus uses the global scrape\n                        timeout unless it is less than the target's scrape interval\n                        value in which the latter is used.\"\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    targetPort:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: \"Name or number of the target port of the `Pod`\n                        object behind the Service, the port must be specified with\n                        container port property. \\n Deprecated: use 'port' instead.\"\n                      x-kubernetes-int-or-string: true\n                    tlsConfig:\n                      description: TLS configuration to use when scraping the target.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    trackTimestampsStaleness:\n                      description: \"`trackTimestampsStaleness` defines whether Prometheus\n                        tracks staleness of the metrics that have an explicit timestamp\n                        present in scraped data. Has no effect if `honorTimestamps`\n                        is false. \\n It requires Prometheus >= v2.48.0.\"\n                      type: boolean\n                  type: object\n                type: array\n              podTargetLabels:\n                description: '`podTargetLabels` defines the labels which are transferred\n                  from the associated Kubernetes `Pod` object onto the ingested metrics.'\n                items:\n                  type: string\n                type: array\n              sampleLimit:\n                description: '`sampleLimit` defines a per-scrape limit on the number\n                  of scraped samples that will be accepted.'\n                format: int64\n                type: integer\n              selector:\n                description: Label selector to select the Kubernetes `Pod` objects.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              targetLimit:\n                description: '`targetLimit` defines a limit on the number of scraped\n                  targets that will be accepted.'\n                format: int64\n                type: integer\n            required:\n            - selector\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: scrapeconfigs.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: ScrapeConfig\n    listKind: ScrapeConfigList\n    plural: scrapeconfigs\n    shortNames:\n    - scfg\n    singular: scrapeconfig\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: ScrapeConfig defines a namespaced Prometheus scrape_config to\n          be aggregated across multiple namespaces into the Prometheus configuration.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: ScrapeConfigSpec is a specification of the desired configuration\n              for a scrape configuration.\n            properties:\n              authorization:\n                description: Authorization header to use on every scrape request.\n                properties:\n                  credentials:\n                    description: Selects a key of a Secret in the namespace that contains\n                      the credentials for authentication.\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  type:\n                    description: \"Defines the authentication type. The value is case-insensitive.\n                      \\n \\\"Basic\\\" is not a supported value. \\n Default: \\\"Bearer\\\"\"\n                    type: string\n                type: object\n              azureSDConfigs:\n                description: AzureSDConfigs defines a list of Azure service discovery\n                  configurations.\n                items:\n                  description: AzureSDConfig allow retrieving scrape targets from\n                    Azure VMs. See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#azure_sd_config\n                  properties:\n                    authenticationMethod:\n                      description: '# The authentication method, either OAuth or ManagedIdentity.\n                        See https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview'\n                      enum:\n                      - OAuth\n                      - ManagedIdentity\n                      type: string\n                    clientID:\n                      description: Optional client ID. Only required with the OAuth\n                        authentication method.\n                      type: string\n                    clientSecret:\n                      description: Optional client secret. Only required with the\n                        OAuth authentication method.\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    environment:\n                      description: The Azure environment.\n                      type: string\n                    port:\n                      description: The port to scrape metrics from. If using the public\n                        IP address, this must instead be specified in the relabeling\n                        rule.\n                      type: integer\n                    refreshInterval:\n                      description: RefreshInterval configures the refresh interval\n                        at which Prometheus will re-read the instance list.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    resourceGroup:\n                      description: Optional resource group name. Limits discovery\n                        to this resource group.\n                      type: string\n                    subscriptionID:\n                      description: The subscription ID. Always required.\n                      minLength: 1\n                      type: string\n                    tenantID:\n                      description: Optional tenant ID. Only required with the OAuth\n                        authentication method.\n                      type: string\n                  required:\n                  - subscriptionID\n                  type: object\n                type: array\n              basicAuth:\n                description: BasicAuth information to use on every scrape request.\n                properties:\n                  password:\n                    description: '`password` specifies a key of a Secret containing\n                      the password for authentication.'\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  username:\n                    description: '`username` specifies a key of a Secret containing\n                      the username for authentication.'\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                type: object\n              consulSDConfigs:\n                description: ConsulSDConfigs defines a list of Consul service discovery\n                  configurations.\n                items:\n                  description: ConsulSDConfig defines a Consul service discovery configuration\n                    See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config\n                  properties:\n                    allowStale:\n                      description: Allow stale Consul results (see https://www.consul.io/api/features/consistency.html).\n                        Will reduce load on Consul. If unset, Prometheus uses its\n                        default value.\n                      type: boolean\n                    authorization:\n                      description: Authorization header configuration to authenticate\n                        against the Consul Server.\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    basicAuth:\n                      description: 'BasicAuth information to authenticate against\n                        the Consul Server. More info: https://prometheus.io/docs/operating/configuration/#endpoints'\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    datacenter:\n                      description: Consul Datacenter name, if not provided it will\n                        use the local Consul Agent Datacenter.\n                      type: string\n                    enableHTTP2:\n                      description: Whether to enable HTTP2. If unset, Prometheus uses\n                        its default value.\n                      type: boolean\n                    followRedirects:\n                      description: Configure whether HTTP requests follow HTTP 3xx\n                        redirects. If unset, Prometheus uses its default value.\n                      type: boolean\n                    namespace:\n                      description: Namespaces are only supported in Consul Enterprise.\n                      type: string\n                    noProxy:\n                      description: \"`noProxy` is a comma-separated string that can\n                        contain IPs, CIDR notation, domain names that should be excluded\n                        from proxying. IP and domain names can contain port numbers.\n                        \\n It requires Prometheus >= v2.43.0.\"\n                      type: string\n                    nodeMeta:\n                      additionalProperties:\n                        type: string\n                      description: Node metadata key/value pairs to filter nodes for\n                        a given service.\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    oauth2:\n                      description: Optional OAuth 2.0 configuration.\n                      properties:\n                        clientId:\n                          description: '`clientId` specifies a key of a Secret or\n                            ConfigMap containing the OAuth2 client''s ID.'\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        clientSecret:\n                          description: '`clientSecret` specifies a key of a Secret\n                            containing the OAuth2 client''s secret.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        endpointParams:\n                          additionalProperties:\n                            type: string\n                          description: '`endpointParams` configures the HTTP parameters\n                            to append to the token URL.'\n                          type: object\n                        scopes:\n                          description: '`scopes` defines the OAuth2 scopes used for\n                            the token request.'\n                          items:\n                            type: string\n                          type: array\n                        tokenUrl:\n                          description: '`tokenURL` configures the URL to fetch the\n                            token from.'\n                          minLength: 1\n                          type: string\n                      required:\n                      - clientId\n                      - clientSecret\n                      - tokenUrl\n                      type: object\n                    partition:\n                      description: Admin Partitions are only supported in Consul Enterprise.\n                      type: string\n                    proxyConnectHeader:\n                      additionalProperties:\n                        description: SecretKeySelector selects a key of a Secret.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      description: \"ProxyConnectHeader optionally specifies headers\n                        to send to proxies during CONNECT requests. \\n It requires\n                        Prometheus >= v2.43.0.\"\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    proxyFromEnvironment:\n                      description: \"Whether to use the proxy configuration defined\n                        by environment variables (HTTP_PROXY, HTTPS_PROXY, and NO_PROXY).\n                        If unset, Prometheus uses its default value. \\n It requires\n                        Prometheus >= v2.43.0.\"\n                      type: boolean\n                    proxyUrl:\n                      description: \"`proxyURL` defines the HTTP proxy server to use.\n                        \\n It requires Prometheus >= v2.43.0.\"\n                      pattern: ^http(s)?://.+$\n                      type: string\n                    refreshInterval:\n                      description: The time after which the provided names are refreshed.\n                        On large setup it might be a good idea to increase this value\n                        because the catalog will change all the time. If unset, Prometheus\n                        uses its default value.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    scheme:\n                      description: HTTP Scheme default \"http\"\n                      enum:\n                      - HTTP\n                      - HTTPS\n                      type: string\n                    server:\n                      description: A valid string consisting of a hostname or IP followed\n                        by an optional port number.\n                      minLength: 1\n                      type: string\n                    services:\n                      description: A list of services for which targets are retrieved.\n                        If omitted, all services are scraped.\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    tagSeparator:\n                      description: The string by which Consul tags are joined into\n                        the tag label. If unset, Prometheus uses its default value.\n                      type: string\n                    tags:\n                      description: An optional list of tags used to filter nodes for\n                        a given service. Services must contain all tags in the list.\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    tlsConfig:\n                      description: TLS Config\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    tokenRef:\n                      description: Consul ACL TokenRef, if not provided it will use\n                        the ACL from the local Consul Agent.\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                  required:\n                  - server\n                  type: object\n                type: array\n              dnsSDConfigs:\n                description: DNSSDConfigs defines a list of DNS service discovery\n                  configurations.\n                items:\n                  description: DNSSDConfig allows specifying a set of DNS domain names\n                    which are periodically queried to discover a list of targets.\n                    The DNS servers to be contacted are read from /etc/resolv.conf.\n                    See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config\n                  properties:\n                    names:\n                      description: A list of DNS domain names to be queried.\n                      items:\n                        type: string\n                      minItems: 1\n                      type: array\n                    port:\n                      description: The port number used if the query type is not SRV\n                        Ignored for SRV records\n                      type: integer\n                    refreshInterval:\n                      description: RefreshInterval configures the time after which\n                        the provided names are refreshed. If not set, Prometheus uses\n                        its default value.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    type:\n                      description: The type of DNS query to perform. One of SRV, A,\n                        AAAA or MX. If not set, Prometheus uses its default value.\n                      enum:\n                      - SRV\n                      - A\n                      - AAAA\n                      - MX\n                      type: string\n                  required:\n                  - names\n                  type: object\n                type: array\n              ec2SDConfigs:\n                description: EC2SDConfigs defines a list of EC2 service discovery\n                  configurations.\n                items:\n                  description: EC2SDConfig allow retrieving scrape targets from AWS\n                    EC2 instances. The private IP address is used by default, but\n                    may be changed to the public IP address with relabeling. The IAM\n                    credentials used must have the ec2:DescribeInstances permission\n                    to discover scrape targets See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config\n                  properties:\n                    accessKey:\n                      description: AccessKey is the AWS API key.\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    filters:\n                      description: 'Filters can be used optionally to filter the instance\n                        list by other criteria. Available filter criteria can be found\n                        here: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html\n                        Filter API documentation: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Filter.html'\n                      items:\n                        description: EC2Filter is the configuration for filtering\n                          EC2 instances.\n                        properties:\n                          name:\n                            type: string\n                          values:\n                            items:\n                              type: string\n                            type: array\n                        required:\n                        - name\n                        - values\n                        type: object\n                      type: array\n                    port:\n                      description: The port to scrape metrics from. If using the public\n                        IP address, this must instead be specified in the relabeling\n                        rule.\n                      type: integer\n                    refreshInterval:\n                      description: RefreshInterval configures the refresh interval\n                        at which Prometheus will re-read the instance list.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    region:\n                      description: The AWS region\n                      type: string\n                    roleARN:\n                      description: AWS Role ARN, an alternative to using AWS API keys.\n                      type: string\n                    secretKey:\n                      description: SecretKey is the AWS API secret.\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                  type: object\n                type: array\n              fileSDConfigs:\n                description: FileSDConfigs defines a list of file service discovery\n                  configurations.\n                items:\n                  description: FileSDConfig defines a Prometheus file service discovery\n                    configuration See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config\n                  properties:\n                    files:\n                      description: 'List of files to be used for file discovery. Recommendation:\n                        use absolute paths. While relative paths work, the prometheus-operator\n                        project makes no guarantees about the working directory where\n                        the configuration file is stored. Files must be mounted using\n                        Prometheus.ConfigMaps or Prometheus.Secrets.'\n                      items:\n                        description: SDFile represents a file used for service discovery\n                        pattern: ^[^*]*(\\*[^/]*)?\\.(json|yml|yaml|JSON|YML|YAML)$\n                        type: string\n                      minItems: 1\n                      type: array\n                    refreshInterval:\n                      description: RefreshInterval configures the refresh interval\n                        at which Prometheus will reload the content of the files.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                  required:\n                  - files\n                  type: object\n                type: array\n              gceSDConfigs:\n                description: GCESDConfigs defines a list of GCE service discovery\n                  configurations.\n                items:\n                  description: \"GCESDConfig configures scrape targets from GCP GCE\n                    instances. The private IP address is used by default, but may\n                    be changed to the public IP address with relabeling. See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config\n                    \\n The GCE service discovery will load the Google Cloud credentials\n                    from the file specified by the GOOGLE_APPLICATION_CREDENTIALS\n                    environment variable. See https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform\n                    \\n A pre-requisite for using GCESDConfig is that a Secret containing\n                    valid Google Cloud credentials is mounted into the Prometheus\n                    or PrometheusAgent pod via the `.spec.secrets` field and that\n                    the GOOGLE_APPLICATION_CREDENTIALS environment variable is set\n                    to /etc/prometheus/secrets/<secret-name>/<credentials-filename.json>.\"\n                  properties:\n                    filter:\n                      description: 'Filter can be used optionally to filter the instance\n                        list by other criteria Syntax of this filter is described\n                        in the filter query parameter section: https://cloud.google.com/compute/docs/reference/latest/instances/list'\n                      type: string\n                    port:\n                      description: The port to scrape metrics from. If using the public\n                        IP address, this must instead be specified in the relabeling\n                        rule.\n                      type: integer\n                    project:\n                      description: The Google Cloud Project ID\n                      minLength: 1\n                      type: string\n                    refreshInterval:\n                      description: RefreshInterval configures the refresh interval\n                        at which Prometheus will re-read the instance list.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    tagSeparator:\n                      description: The tag separator is used to separate the tags\n                        on concatenation\n                      type: string\n                    zone:\n                      description: The zone of the scrape targets. If you need multiple\n                        zones use multiple GCESDConfigs.\n                      minLength: 1\n                      type: string\n                  required:\n                  - project\n                  - zone\n                  type: object\n                type: array\n              honorLabels:\n                description: HonorLabels chooses the metric's labels on collisions\n                  with target labels.\n                type: boolean\n              honorTimestamps:\n                description: HonorTimestamps controls whether Prometheus respects\n                  the timestamps present in scraped data.\n                type: boolean\n              httpSDConfigs:\n                description: HTTPSDConfigs defines a list of HTTP service discovery\n                  configurations.\n                items:\n                  description: HTTPSDConfig defines a prometheus HTTP service discovery\n                    configuration See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#http_sd_config\n                  properties:\n                    authorization:\n                      description: Authorization header configuration to authenticate\n                        against the target HTTP endpoint.\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    basicAuth:\n                      description: 'BasicAuth information to authenticate against\n                        the target HTTP endpoint. More info: https://prometheus.io/docs/operating/configuration/#endpoints'\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    noProxy:\n                      description: \"`noProxy` is a comma-separated string that can\n                        contain IPs, CIDR notation, domain names that should be excluded\n                        from proxying. IP and domain names can contain port numbers.\n                        \\n It requires Prometheus >= v2.43.0.\"\n                      type: string\n                    proxyConnectHeader:\n                      additionalProperties:\n                        description: SecretKeySelector selects a key of a Secret.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      description: \"ProxyConnectHeader optionally specifies headers\n                        to send to proxies during CONNECT requests. \\n It requires\n                        Prometheus >= v2.43.0.\"\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    proxyFromEnvironment:\n                      description: \"Whether to use the proxy configuration defined\n                        by environment variables (HTTP_PROXY, HTTPS_PROXY, and NO_PROXY).\n                        If unset, Prometheus uses its default value. \\n It requires\n                        Prometheus >= v2.43.0.\"\n                      type: boolean\n                    proxyUrl:\n                      description: \"`proxyURL` defines the HTTP proxy server to use.\n                        \\n It requires Prometheus >= v2.43.0.\"\n                      pattern: ^http(s)?://.+$\n                      type: string\n                    refreshInterval:\n                      description: RefreshInterval configures the refresh interval\n                        at which Prometheus will re-query the endpoint to update the\n                        target list.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    tlsConfig:\n                      description: TLS configuration applying to the target HTTP endpoint.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    url:\n                      description: URL from which the targets are fetched.\n                      minLength: 1\n                      pattern: ^http(s)?://.+$\n                      type: string\n                  required:\n                  - url\n                  type: object\n                type: array\n              keepDroppedTargets:\n                description: \"Per-scrape limit on the number of targets dropped by\n                  relabeling that will be kept in memory. 0 means no limit. \\n It\n                  requires Prometheus >= v2.47.0.\"\n                format: int64\n                type: integer\n              kubernetesSDConfigs:\n                description: KubernetesSDConfigs defines a list of Kubernetes service\n                  discovery configurations.\n                items:\n                  description: KubernetesSDConfig allows retrieving scrape targets\n                    from Kubernetes' REST API. See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config\n                  properties:\n                    apiServer:\n                      description: The API server address consisting of a hostname\n                        or IP address followed by an optional port number. If left\n                        empty, Prometheus is assumed to run inside of the cluster.\n                        It will discover API servers automatically and use the pod's\n                        CA certificate and bearer token file at /var/run/secrets/kubernetes.io/serviceaccount/.\n                      type: string\n                    attachMetadata:\n                      description: Optional metadata to attach to discovered targets.\n                        It requires Prometheus >= v2.35.0 for `pod` role and Prometheus\n                        >= v2.37.0 for `endpoints` and `endpointslice` roles.\n                      properties:\n                        node:\n                          description: Attaches node metadata to discovered targets.\n                            When set to true, Prometheus must have the `get` permission\n                            on the `Nodes` objects. Only valid for Pod, Endpoint and\n                            Endpointslice roles.\n                          type: boolean\n                      type: object\n                    authorization:\n                      description: Authorization header to use on every scrape request.\n                        Cannot be set at the same time as `basicAuth`, or `oauth2`.\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    basicAuth:\n                      description: BasicAuth information to use on every scrape request.\n                        Cannot be set at the same time as `authorization`, or `oauth2`.\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    enableHTTP2:\n                      description: Whether to enable HTTP2.\n                      type: boolean\n                    followRedirects:\n                      description: Configure whether HTTP requests follow HTTP 3xx\n                        redirects.\n                      type: boolean\n                    namespaces:\n                      description: Optional namespace discovery. If omitted, Prometheus\n                        discovers targets across all namespaces.\n                      properties:\n                        names:\n                          description: List of namespaces where to watch for resources.\n                            If empty and `ownNamespace` isn't true, Prometheus watches\n                            for resources in all namespaces.\n                          items:\n                            type: string\n                          type: array\n                        ownNamespace:\n                          description: Includes the namespace in which the Prometheus\n                            pod exists to the list of watched namesapces.\n                          type: boolean\n                      type: object\n                    noProxy:\n                      description: \"`noProxy` is a comma-separated string that can\n                        contain IPs, CIDR notation, domain names that should be excluded\n                        from proxying. IP and domain names can contain port numbers.\n                        \\n It requires Prometheus >= v2.43.0.\"\n                      type: string\n                    oauth2:\n                      description: Optional OAuth 2.0 configuration. Cannot be set\n                        at the same time as `authorization`, or `basicAuth`.\n                      properties:\n                        clientId:\n                          description: '`clientId` specifies a key of a Secret or\n                            ConfigMap containing the OAuth2 client''s ID.'\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        clientSecret:\n                          description: '`clientSecret` specifies a key of a Secret\n                            containing the OAuth2 client''s secret.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        endpointParams:\n                          additionalProperties:\n                            type: string\n                          description: '`endpointParams` configures the HTTP parameters\n                            to append to the token URL.'\n                          type: object\n                        scopes:\n                          description: '`scopes` defines the OAuth2 scopes used for\n                            the token request.'\n                          items:\n                            type: string\n                          type: array\n                        tokenUrl:\n                          description: '`tokenURL` configures the URL to fetch the\n                            token from.'\n                          minLength: 1\n                          type: string\n                      required:\n                      - clientId\n                      - clientSecret\n                      - tokenUrl\n                      type: object\n                    proxyConnectHeader:\n                      additionalProperties:\n                        description: SecretKeySelector selects a key of a Secret.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      description: \"ProxyConnectHeader optionally specifies headers\n                        to send to proxies during CONNECT requests. \\n It requires\n                        Prometheus >= v2.43.0.\"\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    proxyFromEnvironment:\n                      description: \"Whether to use the proxy configuration defined\n                        by environment variables (HTTP_PROXY, HTTPS_PROXY, and NO_PROXY).\n                        If unset, Prometheus uses its default value. \\n It requires\n                        Prometheus >= v2.43.0.\"\n                      type: boolean\n                    proxyUrl:\n                      description: \"`proxyURL` defines the HTTP proxy server to use.\n                        \\n It requires Prometheus >= v2.43.0.\"\n                      pattern: ^http(s)?://.+$\n                      type: string\n                    role:\n                      description: Role of the Kubernetes entities that should be\n                        discovered.\n                      enum:\n                      - Node\n                      - node\n                      - Service\n                      - service\n                      - Pod\n                      - pod\n                      - Endpoints\n                      - endpoints\n                      - EndpointSlice\n                      - endpointslice\n                      - Ingress\n                      - ingress\n                      type: string\n                    selectors:\n                      description: Selector to select objects.\n                      items:\n                        description: K8SSelectorConfig is Kubernetes Selector Config\n                        properties:\n                          field:\n                            type: string\n                          label:\n                            type: string\n                          role:\n                            description: Role is role of the service in Kubernetes.\n                            enum:\n                            - Node\n                            - node\n                            - Service\n                            - service\n                            - Pod\n                            - pod\n                            - Endpoints\n                            - endpoints\n                            - EndpointSlice\n                            - endpointslice\n                            - Ingress\n                            - ingress\n                            type: string\n                        required:\n                        - role\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - role\n                      x-kubernetes-list-type: map\n                    tlsConfig:\n                      description: TLS configuration to use on every scrape request.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                  required:\n                  - role\n                  type: object\n                type: array\n              labelLimit:\n                description: Per-scrape limit on number of labels that will be accepted\n                  for a sample. Only valid in Prometheus versions 2.27.0 and newer.\n                format: int64\n                type: integer\n              labelNameLengthLimit:\n                description: Per-scrape limit on length of labels name that will be\n                  accepted for a sample. Only valid in Prometheus versions 2.27.0\n                  and newer.\n                format: int64\n                type: integer\n              labelValueLengthLimit:\n                description: Per-scrape limit on length of labels value that will\n                  be accepted for a sample. Only valid in Prometheus versions 2.27.0\n                  and newer.\n                format: int64\n                type: integer\n              metricRelabelings:\n                description: MetricRelabelConfigs to apply to samples before ingestion.\n                items:\n                  description: \"RelabelConfig allows dynamic rewriting of the label\n                    set for targets, alerts, scraped samples and remote write samples.\n                    \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                  properties:\n                    action:\n                      default: replace\n                      description: \"Action to perform based on the regex matching.\n                        \\n `Uppercase` and `Lowercase` actions require Prometheus\n                        >= v2.36.0. `DropEqual` and `KeepEqual` actions require Prometheus\n                        >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                      enum:\n                      - replace\n                      - Replace\n                      - keep\n                      - Keep\n                      - drop\n                      - Drop\n                      - hashmod\n                      - HashMod\n                      - labelmap\n                      - LabelMap\n                      - labeldrop\n                      - LabelDrop\n                      - labelkeep\n                      - LabelKeep\n                      - lowercase\n                      - Lowercase\n                      - uppercase\n                      - Uppercase\n                      - keepequal\n                      - KeepEqual\n                      - dropequal\n                      - DropEqual\n                      type: string\n                    modulus:\n                      description: \"Modulus to take of the hash of the source label\n                        values. \\n Only applicable when the action is `HashMod`.\"\n                      format: int64\n                      type: integer\n                    regex:\n                      description: Regular expression against which the extracted\n                        value is matched.\n                      type: string\n                    replacement:\n                      description: \"Replacement value against which a Replace action\n                        is performed if the regular expression matches. \\n Regex capture\n                        groups are available.\"\n                      type: string\n                    separator:\n                      description: Separator is the string between concatenated SourceLabels.\n                      type: string\n                    sourceLabels:\n                      description: The source labels select values from existing labels.\n                        Their content is concatenated using the configured Separator\n                        and matched against the configured regular expression.\n                      items:\n                        description: LabelName is a valid Prometheus label name which\n                          may only contain ASCII letters, numbers, as well as underscores.\n                        pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                        type: string\n                      type: array\n                    targetLabel:\n                      description: \"Label to which the resulting string is written\n                        in a replacement. \\n It is mandatory for `Replace`, `HashMod`,\n                        `Lowercase`, `Uppercase`, `KeepEqual` and `DropEqual` actions.\n                        \\n Regex capture groups are available.\"\n                      type: string\n                  type: object\n                type: array\n              metricsPath:\n                description: MetricsPath HTTP path to scrape for metrics. If empty,\n                  Prometheus uses the default value (e.g. /metrics).\n                type: string\n              noProxy:\n                description: \"`noProxy` is a comma-separated string that can contain\n                  IPs, CIDR notation, domain names that should be excluded from proxying.\n                  IP and domain names can contain port numbers. \\n It requires Prometheus\n                  >= v2.43.0.\"\n                type: string\n              openstackSDConfigs:\n                description: OpenStackSDConfigs defines a list of OpenStack service\n                  discovery configurations.\n                items:\n                  description: OpenStackSDConfig allow retrieving scrape targets from\n                    OpenStack Nova instances. See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#openstack_sd_config\n                  properties:\n                    allTenants:\n                      description: Whether the service discovery should list all instances\n                        for all projects. It is only relevant for the 'instance' role\n                        and usually requires admin permissions.\n                      type: boolean\n                    applicationCredentialId:\n                      description: ApplicationCredentialID\n                      type: string\n                    applicationCredentialName:\n                      description: The ApplicationCredentialID or ApplicationCredentialName\n                        fields are required if using an application credential to\n                        authenticate. Some providers allow you to create an application\n                        credential to authenticate rather than a password.\n                      type: string\n                    applicationCredentialSecret:\n                      description: The applicationCredentialSecret field is required\n                        if using an application credential to authenticate.\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    availability:\n                      description: Availability of the endpoint to connect to.\n                      enum:\n                      - Public\n                      - public\n                      - Admin\n                      - admin\n                      - Internal\n                      - internal\n                      type: string\n                    domainID:\n                      description: DomainID\n                      type: string\n                    domainName:\n                      description: At most one of domainId and domainName must be\n                        provided if using username with Identity V3. Otherwise, either\n                        are optional.\n                      type: string\n                    identityEndpoint:\n                      description: IdentityEndpoint specifies the HTTP endpoint that\n                        is required to work with the Identity API of the appropriate\n                        version.\n                      type: string\n                    password:\n                      description: Password for the Identity V2 and V3 APIs. Consult\n                        with your provider's control panel to discover your account's\n                        preferred method of authentication.\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    port:\n                      description: The port to scrape metrics from. If using the public\n                        IP address, this must instead be specified in the relabeling\n                        rule.\n                      type: integer\n                    projectID:\n                      description: ProjectID\n                      type: string\n                    projectName:\n                      description: The ProjectId and ProjectName fields are optional\n                        for the Identity V2 API. Some providers allow you to specify\n                        a ProjectName instead of the ProjectId. Some require both.\n                        Your provider's authentication policies will determine how\n                        these fields influence authentication.\n                      type: string\n                    refreshInterval:\n                      description: Refresh interval to re-read the instance list.\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    region:\n                      description: The OpenStack Region.\n                      minLength: 1\n                      type: string\n                    role:\n                      description: The OpenStack role of entities that should be discovered.\n                      enum:\n                      - Instance\n                      - instance\n                      - Hypervisor\n                      - hypervisor\n                      type: string\n                    tlsConfig:\n                      description: TLS configuration applying to the target HTTP endpoint.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    userid:\n                      description: UserID\n                      type: string\n                    username:\n                      description: Username is required if using Identity V2 API.\n                        Consult with your provider's control panel to discover your\n                        account's username. In Identity V3, either userid or a combination\n                        of username and domainId or domainName are needed\n                      type: string\n                  required:\n                  - region\n                  - role\n                  type: object\n                type: array\n              params:\n                additionalProperties:\n                  items:\n                    type: string\n                  type: array\n                description: Optional HTTP URL parameters\n                type: object\n                x-kubernetes-map-type: atomic\n              proxyConnectHeader:\n                additionalProperties:\n                  description: SecretKeySelector selects a key of a Secret.\n                  properties:\n                    key:\n                      description: The key of the secret to select from.  Must be\n                        a valid secret key.\n                      type: string\n                    name:\n                      description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                        TODO: Add other useful fields. apiVersion, kind, uid?'\n                      type: string\n                    optional:\n                      description: Specify whether the Secret or its key must be defined\n                      type: boolean\n                  required:\n                  - key\n                  type: object\n                  x-kubernetes-map-type: atomic\n                description: \"ProxyConnectHeader optionally specifies headers to send\n                  to proxies during CONNECT requests. \\n It requires Prometheus >=\n                  v2.43.0.\"\n                type: object\n                x-kubernetes-map-type: atomic\n              proxyFromEnvironment:\n                description: \"Whether to use the proxy configuration defined by environment\n                  variables (HTTP_PROXY, HTTPS_PROXY, and NO_PROXY). If unset, Prometheus\n                  uses its default value. \\n It requires Prometheus >= v2.43.0.\"\n                type: boolean\n              proxyUrl:\n                description: \"`proxyURL` defines the HTTP proxy server to use. \\n\n                  It requires Prometheus >= v2.43.0.\"\n                pattern: ^http(s)?://.+$\n                type: string\n              relabelings:\n                description: 'RelabelConfigs defines how to rewrite the target''s\n                  labels before scraping. Prometheus Operator automatically adds relabelings\n                  for a few standard Kubernetes fields. The original scrape job''s\n                  name is available via the `__tmp_prometheus_job_name` label. More\n                  info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'\n                items:\n                  description: \"RelabelConfig allows dynamic rewriting of the label\n                    set for targets, alerts, scraped samples and remote write samples.\n                    \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                  properties:\n                    action:\n                      default: replace\n                      description: \"Action to perform based on the regex matching.\n                        \\n `Uppercase` and `Lowercase` actions require Prometheus\n                        >= v2.36.0. `DropEqual` and `KeepEqual` actions require Prometheus\n                        >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                      enum:\n                      - replace\n                      - Replace\n                      - keep\n                      - Keep\n                      - drop\n                      - Drop\n                      - hashmod\n                      - HashMod\n                      - labelmap\n                      - LabelMap\n                      - labeldrop\n                      - LabelDrop\n                      - labelkeep\n                      - LabelKeep\n                      - lowercase\n                      - Lowercase\n                      - uppercase\n                      - Uppercase\n                      - keepequal\n                      - KeepEqual\n                      - dropequal\n                      - DropEqual\n                      type: string\n                    modulus:\n                      description: \"Modulus to take of the hash of the source label\n                        values. \\n Only applicable when the action is `HashMod`.\"\n                      format: int64\n                      type: integer\n                    regex:\n                      description: Regular expression against which the extracted\n                        value is matched.\n                      type: string\n                    replacement:\n                      description: \"Replacement value against which a Replace action\n                        is performed if the regular expression matches. \\n Regex capture\n                        groups are available.\"\n                      type: string\n                    separator:\n                      description: Separator is the string between concatenated SourceLabels.\n                      type: string\n                    sourceLabels:\n                      description: The source labels select values from existing labels.\n                        Their content is concatenated using the configured Separator\n                        and matched against the configured regular expression.\n                      items:\n                        description: LabelName is a valid Prometheus label name which\n                          may only contain ASCII letters, numbers, as well as underscores.\n                        pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                        type: string\n                      type: array\n                    targetLabel:\n                      description: \"Label to which the resulting string is written\n                        in a replacement. \\n It is mandatory for `Replace`, `HashMod`,\n                        `Lowercase`, `Uppercase`, `KeepEqual` and `DropEqual` actions.\n                        \\n Regex capture groups are available.\"\n                      type: string\n                  type: object\n                type: array\n              sampleLimit:\n                description: SampleLimit defines per-scrape limit on number of scraped\n                  samples that will be accepted.\n                format: int64\n                type: integer\n              scheme:\n                description: Configures the protocol scheme used for requests. If\n                  empty, Prometheus uses HTTP by default.\n                enum:\n                - HTTP\n                - HTTPS\n                type: string\n              scrapeInterval:\n                description: ScrapeInterval is the interval between consecutive scrapes.\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              scrapeTimeout:\n                description: ScrapeTimeout is the number of seconds to wait until\n                  a scrape request times out.\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              staticConfigs:\n                description: StaticConfigs defines a list of static targets with a\n                  common label set.\n                items:\n                  description: StaticConfig defines a Prometheus static configuration.\n                    See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config\n                  properties:\n                    labels:\n                      additionalProperties:\n                        type: string\n                      description: Labels assigned to all metrics scraped from the\n                        targets.\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    targets:\n                      description: List of targets for this static configuration.\n                      items:\n                        description: Target represents a target for Prometheus to\n                          scrape\n                        type: string\n                      type: array\n                  type: object\n                type: array\n              targetLimit:\n                description: TargetLimit defines a limit on the number of scraped\n                  targets that will be accepted.\n                format: int64\n                type: integer\n              tlsConfig:\n                description: TLS configuration to use on every scrape request\n                properties:\n                  ca:\n                    description: Certificate authority used when verifying server\n                      certificates.\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  cert:\n                    description: Client certificate to present when doing client-authentication.\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  insecureSkipVerify:\n                    description: Disable target certificate validation.\n                    type: boolean\n                  keySecret:\n                    description: Secret containing the client key file for the targets.\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  serverName:\n                    description: Used to verify the hostname for the targets.\n                    type: string\n                type: object\n              trackTimestampsStaleness:\n                description: TrackTimestampsStaleness whether Prometheus tracks staleness\n                  of the metrics that have an explicit timestamp present in scraped\n                  data. Has no effect if `honorTimestamps` is false. It requires Prometheus\n                  >= v2.48.0.\n                type: boolean\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: servicemonitors.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: ServiceMonitor\n    listKind: ServiceMonitorList\n    plural: servicemonitors\n    shortNames:\n    - smon\n    singular: servicemonitor\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: ServiceMonitor defines monitoring for a set of services.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Specification of desired Service selection for target discovery\n              by Prometheus.\n            properties:\n              attachMetadata:\n                description: \"`attachMetadata` defines additional metadata which is\n                  added to the discovered targets. \\n It requires Prometheus >= v2.37.0.\"\n                properties:\n                  node:\n                    description: When set to true, Prometheus must have the `get`\n                      permission on the `Nodes` objects.\n                    type: boolean\n                type: object\n              endpoints:\n                description: List of endpoints part of this ServiceMonitor.\n                items:\n                  description: Endpoint defines an endpoint serving Prometheus metrics\n                    to be scraped by Prometheus.\n                  properties:\n                    authorization:\n                      description: \"`authorization` configures the Authorization header\n                        credentials to use when scraping the target. \\n Cannot be\n                        set at the same time as `basicAuth`, or `oauth2`.\"\n                      properties:\n                        credentials:\n                          description: Selects a key of a Secret in the namespace\n                            that contains the credentials for authentication.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        type:\n                          description: \"Defines the authentication type. The value\n                            is case-insensitive. \\n \\\"Basic\\\" is not a supported value.\n                            \\n Default: \\\"Bearer\\\"\"\n                          type: string\n                      type: object\n                    basicAuth:\n                      description: \"`basicAuth` configures the Basic Authentication\n                        credentials to use when scraping the target. \\n Cannot be\n                        set at the same time as `authorization`, or `oauth2`.\"\n                      properties:\n                        password:\n                          description: '`password` specifies a key of a Secret containing\n                            the password for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        username:\n                          description: '`username` specifies a key of a Secret containing\n                            the username for authentication.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      type: object\n                    bearerTokenFile:\n                      description: \"File to read bearer token for scraping the target.\n                        \\n Deprecated: use `authorization` instead.\"\n                      type: string\n                    bearerTokenSecret:\n                      description: \"`bearerTokenSecret` specifies a key of a Secret\n                        containing the bearer token for scraping targets. The secret\n                        needs to be in the same namespace as the ServiceMonitor object\n                        and readable by the Prometheus Operator. \\n Deprecated: use\n                        `authorization` instead.\"\n                      properties:\n                        key:\n                          description: The key of the secret to select from.  Must\n                            be a valid secret key.\n                          type: string\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: Specify whether the Secret or its key must\n                            be defined\n                          type: boolean\n                      required:\n                      - key\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    enableHttp2:\n                      description: '`enableHttp2` can be used to disable HTTP2 when\n                        scraping the target.'\n                      type: boolean\n                    filterRunning:\n                      description: \"When true, the pods which are not running (e.g.\n                        either in Failed or Succeeded state) are dropped during the\n                        target discovery. \\n If unset, the filtering is enabled. \\n\n                        More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase\"\n                      type: boolean\n                    followRedirects:\n                      description: '`followRedirects` defines whether the scrape requests\n                        should follow HTTP 3xx redirects.'\n                      type: boolean\n                    honorLabels:\n                      description: When true, `honorLabels` preserves the metric's\n                        labels when they collide with the target's labels.\n                      type: boolean\n                    honorTimestamps:\n                      description: '`honorTimestamps` controls whether Prometheus\n                        preserves the timestamps when exposed by the target.'\n                      type: boolean\n                    interval:\n                      description: \"Interval at which Prometheus scrapes the metrics\n                        from the target. \\n If empty, Prometheus uses the global scrape\n                        interval.\"\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    metricRelabelings:\n                      description: '`metricRelabelings` configures the relabeling\n                        rules to apply to the samples before ingestion.'\n                      items:\n                        description: \"RelabelConfig allows dynamic rewriting of the\n                          label set for targets, alerts, scraped samples and remote\n                          write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                        properties:\n                          action:\n                            default: replace\n                            description: \"Action to perform based on the regex matching.\n                              \\n `Uppercase` and `Lowercase` actions require Prometheus\n                              >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                              Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                            enum:\n                            - replace\n                            - Replace\n                            - keep\n                            - Keep\n                            - drop\n                            - Drop\n                            - hashmod\n                            - HashMod\n                            - labelmap\n                            - LabelMap\n                            - labeldrop\n                            - LabelDrop\n                            - labelkeep\n                            - LabelKeep\n                            - lowercase\n                            - Lowercase\n                            - uppercase\n                            - Uppercase\n                            - keepequal\n                            - KeepEqual\n                            - dropequal\n                            - DropEqual\n                            type: string\n                          modulus:\n                            description: \"Modulus to take of the hash of the source\n                              label values. \\n Only applicable when the action is\n                              `HashMod`.\"\n                            format: int64\n                            type: integer\n                          regex:\n                            description: Regular expression against which the extracted\n                              value is matched.\n                            type: string\n                          replacement:\n                            description: \"Replacement value against which a Replace\n                              action is performed if the regular expression matches.\n                              \\n Regex capture groups are available.\"\n                            type: string\n                          separator:\n                            description: Separator is the string between concatenated\n                              SourceLabels.\n                            type: string\n                          sourceLabels:\n                            description: The source labels select values from existing\n                              labels. Their content is concatenated using the configured\n                              Separator and matched against the configured regular\n                              expression.\n                            items:\n                              description: LabelName is a valid Prometheus label name\n                                which may only contain ASCII letters, numbers, as\n                                well as underscores.\n                              pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                              type: string\n                            type: array\n                          targetLabel:\n                            description: \"Label to which the resulting string is written\n                              in a replacement. \\n It is mandatory for `Replace`,\n                              `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                              `DropEqual` actions. \\n Regex capture groups are available.\"\n                            type: string\n                        type: object\n                      type: array\n                    oauth2:\n                      description: \"`oauth2` configures the OAuth2 settings to use\n                        when scraping the target. \\n It requires Prometheus >= 2.27.0.\n                        \\n Cannot be set at the same time as `authorization`, or `basicAuth`.\"\n                      properties:\n                        clientId:\n                          description: '`clientId` specifies a key of a Secret or\n                            ConfigMap containing the OAuth2 client''s ID.'\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        clientSecret:\n                          description: '`clientSecret` specifies a key of a Secret\n                            containing the OAuth2 client''s secret.'\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        endpointParams:\n                          additionalProperties:\n                            type: string\n                          description: '`endpointParams` configures the HTTP parameters\n                            to append to the token URL.'\n                          type: object\n                        scopes:\n                          description: '`scopes` defines the OAuth2 scopes used for\n                            the token request.'\n                          items:\n                            type: string\n                          type: array\n                        tokenUrl:\n                          description: '`tokenURL` configures the URL to fetch the\n                            token from.'\n                          minLength: 1\n                          type: string\n                      required:\n                      - clientId\n                      - clientSecret\n                      - tokenUrl\n                      type: object\n                    params:\n                      additionalProperties:\n                        items:\n                          type: string\n                        type: array\n                      description: params define optional HTTP URL parameters.\n                      type: object\n                    path:\n                      description: \"HTTP path from which to scrape for metrics. \\n\n                        If empty, Prometheus uses the default value (e.g. `/metrics`).\"\n                      type: string\n                    port:\n                      description: \"Name of the Service port which this endpoint refers\n                        to. \\n It takes precedence over `targetPort`.\"\n                      type: string\n                    proxyUrl:\n                      description: '`proxyURL` configures the HTTP Proxy URL (e.g.\n                        \"http://proxyserver:2195\") to go through when scraping the\n                        target.'\n                      type: string\n                    relabelings:\n                      description: \"`relabelings` configures the relabeling rules\n                        to apply the target's metadata labels. \\n The Operator automatically\n                        adds relabelings for a few standard Kubernetes fields. \\n\n                        The original scrape job's name is available via the `__tmp_prometheus_job_name`\n                        label. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                      items:\n                        description: \"RelabelConfig allows dynamic rewriting of the\n                          label set for targets, alerts, scraped samples and remote\n                          write samples. \\n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\"\n                        properties:\n                          action:\n                            default: replace\n                            description: \"Action to perform based on the regex matching.\n                              \\n `Uppercase` and `Lowercase` actions require Prometheus\n                              >= v2.36.0. `DropEqual` and `KeepEqual` actions require\n                              Prometheus >= v2.41.0. \\n Default: \\\"Replace\\\"\"\n                            enum:\n                            - replace\n                            - Replace\n                            - keep\n                            - Keep\n                            - drop\n                            - Drop\n                            - hashmod\n                            - HashMod\n                            - labelmap\n                            - LabelMap\n                            - labeldrop\n                            - LabelDrop\n                            - labelkeep\n                            - LabelKeep\n                            - lowercase\n                            - Lowercase\n                            - uppercase\n                            - Uppercase\n                            - keepequal\n                            - KeepEqual\n                            - dropequal\n                            - DropEqual\n                            type: string\n                          modulus:\n                            description: \"Modulus to take of the hash of the source\n                              label values. \\n Only applicable when the action is\n                              `HashMod`.\"\n                            format: int64\n                            type: integer\n                          regex:\n                            description: Regular expression against which the extracted\n                              value is matched.\n                            type: string\n                          replacement:\n                            description: \"Replacement value against which a Replace\n                              action is performed if the regular expression matches.\n                              \\n Regex capture groups are available.\"\n                            type: string\n                          separator:\n                            description: Separator is the string between concatenated\n                              SourceLabels.\n                            type: string\n                          sourceLabels:\n                            description: The source labels select values from existing\n                              labels. Their content is concatenated using the configured\n                              Separator and matched against the configured regular\n                              expression.\n                            items:\n                              description: LabelName is a valid Prometheus label name\n                                which may only contain ASCII letters, numbers, as\n                                well as underscores.\n                              pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$\n                              type: string\n                            type: array\n                          targetLabel:\n                            description: \"Label to which the resulting string is written\n                              in a replacement. \\n It is mandatory for `Replace`,\n                              `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and\n                              `DropEqual` actions. \\n Regex capture groups are available.\"\n                            type: string\n                        type: object\n                      type: array\n                    scheme:\n                      description: \"HTTP scheme to use for scraping. \\n `http` and\n                        `https` are the expected values unless you rewrite the `__scheme__`\n                        label via relabeling. \\n If empty, Prometheus uses the default\n                        value `http`.\"\n                      enum:\n                      - http\n                      - https\n                      type: string\n                    scrapeTimeout:\n                      description: \"Timeout after which Prometheus considers the scrape\n                        to be failed. \\n If empty, Prometheus uses the global scrape\n                        timeout unless it is less than the target's scrape interval\n                        value in which the latter is used.\"\n                      pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                      type: string\n                    targetPort:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: \"Name or number of the target port of the `Pod`\n                        object behind the Service, the port must be specified with\n                        container port property. \\n Deprecated: use `port` instead.\"\n                      x-kubernetes-int-or-string: true\n                    tlsConfig:\n                      description: TLS configuration to use when scraping the target.\n                      properties:\n                        ca:\n                          description: Certificate authority used when verifying server\n                            certificates.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        caFile:\n                          description: Path to the CA cert in the Prometheus container\n                            to use for the targets.\n                          type: string\n                        cert:\n                          description: Client certificate to present when doing client-authentication.\n                          properties:\n                            configMap:\n                              description: ConfigMap containing data to use for the\n                                targets.\n                              properties:\n                                key:\n                                  description: The key to select.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the ConfigMap or its\n                                    key must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            secret:\n                              description: Secret containing data to use for the targets.\n                              properties:\n                                key:\n                                  description: The key of the secret to select from.  Must\n                                    be a valid secret key.\n                                  type: string\n                                name:\n                                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                    TODO: Add other useful fields. apiVersion, kind,\n                                    uid?'\n                                  type: string\n                                optional:\n                                  description: Specify whether the Secret or its key\n                                    must be defined\n                                  type: boolean\n                              required:\n                              - key\n                              type: object\n                              x-kubernetes-map-type: atomic\n                          type: object\n                        certFile:\n                          description: Path to the client cert file in the Prometheus\n                            container for the targets.\n                          type: string\n                        insecureSkipVerify:\n                          description: Disable target certificate validation.\n                          type: boolean\n                        keyFile:\n                          description: Path to the client key file in the Prometheus\n                            container for the targets.\n                          type: string\n                        keySecret:\n                          description: Secret containing the client key file for the\n                            targets.\n                          properties:\n                            key:\n                              description: The key of the secret to select from.  Must\n                                be a valid secret key.\n                              type: string\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                            optional:\n                              description: Specify whether the Secret or its key must\n                                be defined\n                              type: boolean\n                          required:\n                          - key\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        serverName:\n                          description: Used to verify the hostname for the targets.\n                          type: string\n                      type: object\n                    trackTimestampsStaleness:\n                      description: \"`trackTimestampsStaleness` defines whether Prometheus\n                        tracks staleness of the metrics that have an explicit timestamp\n                        present in scraped data. Has no effect if `honorTimestamps`\n                        is false. \\n It requires Prometheus >= v2.48.0.\"\n                      type: boolean\n                  type: object\n                type: array\n              jobLabel:\n                description: \"`jobLabel` selects the label from the associated Kubernetes\n                  `Service` object which will be used as the `job` label for all metrics.\n                  \\n For example if `jobLabel` is set to `foo` and the Kubernetes\n                  `Service` object is labeled with `foo: bar`, then Prometheus adds\n                  the `job=\\\"bar\\\"` label to all ingested metrics. \\n If the value\n                  of this field is empty or if the label doesn't exist for the given\n                  Service, the `job` label of the metrics defaults to the name of\n                  the associated Kubernetes `Service`.\"\n                type: string\n              keepDroppedTargets:\n                description: \"Per-scrape limit on the number of targets dropped by\n                  relabeling that will be kept in memory. 0 means no limit. \\n It\n                  requires Prometheus >= v2.47.0.\"\n                format: int64\n                type: integer\n              labelLimit:\n                description: \"Per-scrape limit on number of labels that will be accepted\n                  for a sample. \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              labelNameLengthLimit:\n                description: \"Per-scrape limit on length of labels name that will\n                  be accepted for a sample. \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              labelValueLengthLimit:\n                description: \"Per-scrape limit on length of labels value that will\n                  be accepted for a sample. \\n It requires Prometheus >= v2.27.0.\"\n                format: int64\n                type: integer\n              namespaceSelector:\n                description: Selector to select which namespaces the Kubernetes `Endpoints`\n                  objects are discovered from.\n                properties:\n                  any:\n                    description: Boolean describing whether all namespaces are selected\n                      in contrast to a list restricting them.\n                    type: boolean\n                  matchNames:\n                    description: List of namespace names to select from.\n                    items:\n                      type: string\n                    type: array\n                type: object\n              podTargetLabels:\n                description: '`podTargetLabels` defines the labels which are transferred\n                  from the associated Kubernetes `Pod` object onto the ingested metrics.'\n                items:\n                  type: string\n                type: array\n              sampleLimit:\n                description: '`sampleLimit` defines a per-scrape limit on the number\n                  of scraped samples that will be accepted.'\n                format: int64\n                type: integer\n              selector:\n                description: Label selector to select the Kubernetes `Endpoints` objects.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              targetLabels:\n                description: '`targetLabels` defines the labels which are transferred\n                  from the associated Kubernetes `Service` object onto the ingested\n                  metrics.'\n                items:\n                  type: string\n                type: array\n              targetLimit:\n                description: '`targetLimit` defines a limit on the number of scraped\n                  targets that will be accepted.'\n                format: int64\n                type: integer\n            required:\n            - selector\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n    operator.prometheus.io/version: 0.71.2\n  name: thanosrulers.monitoring.coreos.com\nspec:\n  group: monitoring.coreos.com\n  names:\n    categories:\n    - prometheus-operator\n    kind: ThanosRuler\n    listKind: ThanosRulerList\n    plural: thanosrulers\n    shortNames:\n    - ruler\n    singular: thanosruler\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - description: The version of Thanos Ruler\n      jsonPath: .spec.version\n      name: Version\n      type: string\n    - description: The number of desired replicas\n      jsonPath: .spec.replicas\n      name: Replicas\n      type: integer\n    - description: The number of ready replicas\n      jsonPath: .status.availableReplicas\n      name: Ready\n      type: integer\n    - jsonPath: .status.conditions[?(@.type == 'Reconciled')].status\n      name: Reconciled\n      type: string\n    - jsonPath: .status.conditions[?(@.type == 'Available')].status\n      name: Available\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - description: Whether the resource reconciliation is paused or not\n      jsonPath: .status.paused\n      name: Paused\n      priority: 1\n      type: boolean\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: ThanosRuler defines a ThanosRuler deployment.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: 'Specification of the desired behavior of the ThanosRuler\n              cluster. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n            properties:\n              additionalArgs:\n                description: AdditionalArgs allows setting additional arguments for\n                  the ThanosRuler container. It is intended for e.g. activating hidden\n                  flags which are not supported by the dedicated configuration options\n                  yet. The arguments are passed as-is to the ThanosRuler container\n                  which may cause issues if they are invalid or not supported by the\n                  given ThanosRuler version. In case of an argument conflict (e.g.\n                  an argument which is already set by the operator itself) or when\n                  providing an invalid argument the reconciliation will fail and an\n                  error will be logged.\n                items:\n                  description: Argument as part of the AdditionalArgs list.\n                  properties:\n                    name:\n                      description: Name of the argument, e.g. \"scrape.discovery-reload-interval\".\n                      minLength: 1\n                      type: string\n                    value:\n                      description: Argument value, e.g. 30s. Can be empty for name-only\n                        arguments (e.g. --storage.tsdb.no-lockfile)\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              affinity:\n                description: If specified, the pod's scheduling constraints.\n                properties:\n                  nodeAffinity:\n                    description: Describes node affinity scheduling rules for the\n                      pod.\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the affinity expressions specified by\n                          this field, but it may choose a node that violates one or\n                          more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node matches\n                          the corresponding matchExpressions; the node(s) with the\n                          highest sum are the most preferred.\n                        items:\n                          description: An empty preferred scheduling term matches\n                            all objects with implicit weight 0 (i.e. it's a no-op).\n                            A null preferred scheduling term matches no objects (i.e.\n                            is also a no-op).\n                          properties:\n                            preference:\n                              description: A node selector term, associated with the\n                                corresponding weight.\n                              properties:\n                                matchExpressions:\n                                  description: A list of node selector requirements\n                                    by node's labels.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchFields:\n                                  description: A list of node selector requirements\n                                    by node's fields.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            weight:\n                              description: Weight associated with matching the corresponding\n                                nodeSelectorTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - preference\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the affinity requirements specified by this\n                          field are not met at scheduling time, the pod will not be\n                          scheduled onto the node. If the affinity requirements specified\n                          by this field cease to be met at some point during pod execution\n                          (e.g. due to an update), the system may or may not try to\n                          eventually evict the pod from its node.\n                        properties:\n                          nodeSelectorTerms:\n                            description: Required. A list of node selector terms.\n                              The terms are ORed.\n                            items:\n                              description: A null or empty node selector term matches\n                                no objects. The requirements of them are ANDed. The\n                                TopologySelectorTerm type implements a subset of the\n                                NodeSelectorTerm.\n                              properties:\n                                matchExpressions:\n                                  description: A list of node selector requirements\n                                    by node's labels.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchFields:\n                                  description: A list of node selector requirements\n                                    by node's fields.\n                                  items:\n                                    description: A node selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: The label key that the selector\n                                          applies to.\n                                        type: string\n                                      operator:\n                                        description: Represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists, DoesNotExist. Gt, and\n                                          Lt.\n                                        type: string\n                                      values:\n                                        description: An array of string values. If\n                                          the operator is In or NotIn, the values\n                                          array must be non-empty. If the operator\n                                          is Exists or DoesNotExist, the values array\n                                          must be empty. If the operator is Gt or\n                                          Lt, the values array must have a single\n                                          element, which will be interpreted as an\n                                          integer. This array is replaced during a\n                                          strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            type: array\n                        required:\n                        - nodeSelectorTerms\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  podAffinity:\n                    description: Describes pod affinity scheduling rules (e.g. co-locate\n                      this pod in the same node, zone, etc. as some other pod(s)).\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the affinity expressions specified by\n                          this field, but it may choose a node that violates one or\n                          more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node has\n                          pods which matches the corresponding podAffinityTerm; the\n                          node(s) with the highest sum are the most preferred.\n                        items:\n                          description: The weights of all of the matched WeightedPodAffinityTerm\n                            fields are added per-node to find the most preferred node(s)\n                          properties:\n                            podAffinityTerm:\n                              description: Required. A pod affinity term, associated\n                                with the corresponding weight.\n                              properties:\n                                labelSelector:\n                                  description: A label query over a set of resources,\n                                    in this case pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaceSelector:\n                                  description: A label query over the set of namespaces\n                                    that the term applies to. The term is applied\n                                    to the union of the namespaces selected by this\n                                    field and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list\n                                    means \"this pod's namespace\". An empty selector\n                                    ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: namespaces specifies a static list\n                                    of namespace names that the term applies to. The\n                                    term is applied to the union of the namespaces\n                                    listed in this field and the ones selected by\n                                    namespaceSelector. null or empty namespaces list\n                                    and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                topologyKey:\n                                  description: This pod should be co-located (affinity)\n                                    or not co-located (anti-affinity) with the pods\n                                    matching the labelSelector in the specified namespaces,\n                                    where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey\n                                    matches that of any node on which any of the selected\n                                    pods is running. Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            weight:\n                              description: weight associated with matching the corresponding\n                                podAffinityTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - podAffinityTerm\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the affinity requirements specified by this\n                          field are not met at scheduling time, the pod will not be\n                          scheduled onto the node. If the affinity requirements specified\n                          by this field cease to be met at some point during pod execution\n                          (e.g. due to a pod label update), the system may or may\n                          not try to eventually evict the pod from its node. When\n                          there are multiple elements, the lists of nodes corresponding\n                          to each podAffinityTerm are intersected, i.e. all terms\n                          must be satisfied.\n                        items:\n                          description: Defines a set of pods (namely those matching\n                            the labelSelector relative to the given namespace(s))\n                            that this pod should be co-located (affinity) or not co-located\n                            (anti-affinity) with, where co-located is defined as running\n                            on a node whose value of the label with key <topologyKey>\n                            matches that of any node on which a pod of the set of\n                            pods is running\n                          properties:\n                            labelSelector:\n                              description: A label query over a set of resources,\n                                in this case pods.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaceSelector:\n                              description: A label query over the set of namespaces\n                                that the term applies to. The term is applied to the\n                                union of the namespaces selected by this field and\n                                the ones listed in the namespaces field. null selector\n                                and null or empty namespaces list means \"this pod's\n                                namespace\". An empty selector ({}) matches all namespaces.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaces:\n                              description: namespaces specifies a static list of namespace\n                                names that the term applies to. The term is applied\n                                to the union of the namespaces listed in this field\n                                and the ones selected by namespaceSelector. null or\n                                empty namespaces list and null namespaceSelector means\n                                \"this pod's namespace\".\n                              items:\n                                type: string\n                              type: array\n                            topologyKey:\n                              description: This pod should be co-located (affinity)\n                                or not co-located (anti-affinity) with the pods matching\n                                the labelSelector in the specified namespaces, where\n                                co-located is defined as running on a node whose value\n                                of the label with key topologyKey matches that of\n                                any node on which any of the selected pods is running.\n                                Empty topologyKey is not allowed.\n                              type: string\n                          required:\n                          - topologyKey\n                          type: object\n                        type: array\n                    type: object\n                  podAntiAffinity:\n                    description: Describes pod anti-affinity scheduling rules (e.g.\n                      avoid putting this pod in the same node, zone, etc. as some\n                      other pod(s)).\n                    properties:\n                      preferredDuringSchedulingIgnoredDuringExecution:\n                        description: The scheduler will prefer to schedule pods to\n                          nodes that satisfy the anti-affinity expressions specified\n                          by this field, but it may choose a node that violates one\n                          or more of the expressions. The node that is most preferred\n                          is the one with the greatest sum of weights, i.e. for each\n                          node that meets all of the scheduling requirements (resource\n                          request, requiredDuringScheduling anti-affinity expressions,\n                          etc.), compute a sum by iterating through the elements of\n                          this field and adding \"weight\" to the sum if the node has\n                          pods which matches the corresponding podAffinityTerm; the\n                          node(s) with the highest sum are the most preferred.\n                        items:\n                          description: The weights of all of the matched WeightedPodAffinityTerm\n                            fields are added per-node to find the most preferred node(s)\n                          properties:\n                            podAffinityTerm:\n                              description: Required. A pod affinity term, associated\n                                with the corresponding weight.\n                              properties:\n                                labelSelector:\n                                  description: A label query over a set of resources,\n                                    in this case pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaceSelector:\n                                  description: A label query over the set of namespaces\n                                    that the term applies to. The term is applied\n                                    to the union of the namespaces selected by this\n                                    field and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list\n                                    means \"this pod's namespace\". An empty selector\n                                    ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: namespaces specifies a static list\n                                    of namespace names that the term applies to. The\n                                    term is applied to the union of the namespaces\n                                    listed in this field and the ones selected by\n                                    namespaceSelector. null or empty namespaces list\n                                    and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                topologyKey:\n                                  description: This pod should be co-located (affinity)\n                                    or not co-located (anti-affinity) with the pods\n                                    matching the labelSelector in the specified namespaces,\n                                    where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey\n                                    matches that of any node on which any of the selected\n                                    pods is running. Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            weight:\n                              description: weight associated with matching the corresponding\n                                podAffinityTerm, in the range 1-100.\n                              format: int32\n                              type: integer\n                          required:\n                          - podAffinityTerm\n                          - weight\n                          type: object\n                        type: array\n                      requiredDuringSchedulingIgnoredDuringExecution:\n                        description: If the anti-affinity requirements specified by\n                          this field are not met at scheduling time, the pod will\n                          not be scheduled onto the node. If the anti-affinity requirements\n                          specified by this field cease to be met at some point during\n                          pod execution (e.g. due to a pod label update), the system\n                          may or may not try to eventually evict the pod from its\n                          node. When there are multiple elements, the lists of nodes\n                          corresponding to each podAffinityTerm are intersected, i.e.\n                          all terms must be satisfied.\n                        items:\n                          description: Defines a set of pods (namely those matching\n                            the labelSelector relative to the given namespace(s))\n                            that this pod should be co-located (affinity) or not co-located\n                            (anti-affinity) with, where co-located is defined as running\n                            on a node whose value of the label with key <topologyKey>\n                            matches that of any node on which a pod of the set of\n                            pods is running\n                          properties:\n                            labelSelector:\n                              description: A label query over a set of resources,\n                                in this case pods.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaceSelector:\n                              description: A label query over the set of namespaces\n                                that the term applies to. The term is applied to the\n                                union of the namespaces selected by this field and\n                                the ones listed in the namespaces field. null selector\n                                and null or empty namespaces list means \"this pod's\n                                namespace\". An empty selector ({}) matches all namespaces.\n                              properties:\n                                matchExpressions:\n                                  description: matchExpressions is a list of label\n                                    selector requirements. The requirements are ANDed.\n                                  items:\n                                    description: A label selector requirement is a\n                                      selector that contains values, a key, and an\n                                      operator that relates the key and values.\n                                    properties:\n                                      key:\n                                        description: key is the label key that the\n                                          selector applies to.\n                                        type: string\n                                      operator:\n                                        description: operator represents a key's relationship\n                                          to a set of values. Valid operators are\n                                          In, NotIn, Exists and DoesNotExist.\n                                        type: string\n                                      values:\n                                        description: values is an array of string\n                                          values. If the operator is In or NotIn,\n                                          the values array must be non-empty. If the\n                                          operator is Exists or DoesNotExist, the\n                                          values array must be empty. This array is\n                                          replaced during a strategic merge patch.\n                                        items:\n                                          type: string\n                                        type: array\n                                    required:\n                                    - key\n                                    - operator\n                                    type: object\n                                  type: array\n                                matchLabels:\n                                  additionalProperties:\n                                    type: string\n                                  description: matchLabels is a map of {key,value}\n                                    pairs. A single {key,value} in the matchLabels\n                                    map is equivalent to an element of matchExpressions,\n                                    whose key field is \"key\", the operator is \"In\",\n                                    and the values array contains only \"value\". The\n                                    requirements are ANDed.\n                                  type: object\n                              type: object\n                              x-kubernetes-map-type: atomic\n                            namespaces:\n                              description: namespaces specifies a static list of namespace\n                                names that the term applies to. The term is applied\n                                to the union of the namespaces listed in this field\n                                and the ones selected by namespaceSelector. null or\n                                empty namespaces list and null namespaceSelector means\n                                \"this pod's namespace\".\n                              items:\n                                type: string\n                              type: array\n                            topologyKey:\n                              description: This pod should be co-located (affinity)\n                                or not co-located (anti-affinity) with the pods matching\n                                the labelSelector in the specified namespaces, where\n                                co-located is defined as running on a node whose value\n                                of the label with key topologyKey matches that of\n                                any node on which any of the selected pods is running.\n                                Empty topologyKey is not allowed.\n                              type: string\n                          required:\n                          - topologyKey\n                          type: object\n                        type: array\n                    type: object\n                type: object\n              alertDropLabels:\n                description: AlertDropLabels configure the label names which should\n                  be dropped in ThanosRuler alerts. The replica label `thanos_ruler_replica`\n                  will always be dropped in alerts.\n                items:\n                  type: string\n                type: array\n              alertQueryUrl:\n                description: The external Query URL the Thanos Ruler will set in the\n                  'Source' field of all alerts. Maps to the '--alert.query-url' CLI\n                  arg.\n                type: string\n              alertRelabelConfigFile:\n                description: AlertRelabelConfigFile specifies the path of the alert\n                  relabeling configuration file. When used alongside with AlertRelabelConfigs,\n                  alertRelabelConfigFile takes precedence.\n                type: string\n              alertRelabelConfigs:\n                description: 'AlertRelabelConfigs configures alert relabeling in ThanosRuler.\n                  Alert relabel configurations must have the form as specified in\n                  the official Prometheus documentation: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alert_relabel_configs\n                  Alternative to AlertRelabelConfigFile, and lower order priority.'\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              alertmanagersConfig:\n                description: Define configuration for connecting to alertmanager.  Only\n                  available with thanos v0.10.0 and higher.  Maps to the `alertmanagers.config`\n                  arg.\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              alertmanagersUrl:\n                description: 'Define URLs to send alerts to Alertmanager.  For Thanos\n                  v0.10.0 and higher, AlertManagersConfig should be used instead.  Note:\n                  this field will be ignored if AlertManagersConfig is specified.\n                  Maps to the `alertmanagers.url` arg.'\n                items:\n                  type: string\n                type: array\n              containers:\n                description: 'Containers allows injecting additional containers or\n                  modifying operator generated containers. This can be used to allow\n                  adding an authentication proxy to a ThanosRuler pod or to change\n                  the behavior of an operator generated container. Containers described\n                  here modify an operator generated container if they share the same\n                  name and modifications are done via a strategic merge patch. The\n                  current container names are: `thanos-ruler` and `config-reloader`.\n                  Overriding containers is entirely outside the scope of what the\n                  maintainers will support and by doing so, you accept that this behaviour\n                  may break at any time without notice.'\n                items:\n                  description: A single application container that you want to run\n                    within a pod.\n                  properties:\n                    args:\n                      description: 'Arguments to the entrypoint. The container image''s\n                        CMD is used if this is not provided. Variable references $(VAR_NAME)\n                        are expanded using the container''s environment. If a variable\n                        cannot be resolved, the reference in the input string will\n                        be unchanged. Double $$ are reduced to a single $, which allows\n                        for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\n                        produce the string literal \"$(VAR_NAME)\". Escaped references\n                        will never be expanded, regardless of whether the variable\n                        exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    command:\n                      description: 'Entrypoint array. Not executed within a shell.\n                        The container image''s ENTRYPOINT is used if this is not provided.\n                        Variable references $(VAR_NAME) are expanded using the container''s\n                        environment. If a variable cannot be resolved, the reference\n                        in the input string will be unchanged. Double $$ are reduced\n                        to a single $, which allows for escaping the $(VAR_NAME) syntax:\n                        i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\n                        Escaped references will never be expanded, regardless of whether\n                        the variable exists or not. Cannot be updated. More info:\n                        https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    env:\n                      description: List of environment variables to set in the container.\n                        Cannot be updated.\n                      items:\n                        description: EnvVar represents an environment variable present\n                          in a Container.\n                        properties:\n                          name:\n                            description: Name of the environment variable. Must be\n                              a C_IDENTIFIER.\n                            type: string\n                          value:\n                            description: 'Variable references $(VAR_NAME) are expanded\n                              using the previously defined environment variables in\n                              the container and any service environment variables.\n                              If a variable cannot be resolved, the reference in the\n                              input string will be unchanged. Double $$ are reduced\n                              to a single $, which allows for escaping the $(VAR_NAME)\n                              syntax: i.e. \"$$(VAR_NAME)\" will produce the string\n                              literal \"$(VAR_NAME)\". Escaped references will never\n                              be expanded, regardless of whether the variable exists\n                              or not. Defaults to \"\".'\n                            type: string\n                          valueFrom:\n                            description: Source for the environment variable's value.\n                              Cannot be used if value is not empty.\n                            properties:\n                              configMapKeyRef:\n                                description: Selects a key of a ConfigMap.\n                                properties:\n                                  key:\n                                    description: The key to select.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              fieldRef:\n                                description: 'Selects a field of the pod: supports\n                                  metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`,\n                                  `metadata.annotations[''<KEY>'']`, spec.nodeName,\n                                  spec.serviceAccountName, status.hostIP, status.podIP,\n                                  status.podIPs.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, limits.ephemeral-storage, requests.cpu,\n                                  requests.memory and requests.ephemeral-storage)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              secretKeyRef:\n                                description: Selects a key of a secret in the pod's\n                                  namespace\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    envFrom:\n                      description: List of sources to populate environment variables\n                        in the container. The keys defined within a source must be\n                        a C_IDENTIFIER. All invalid keys will be reported as an event\n                        when the container is starting. When a key exists in multiple\n                        sources, the value associated with the last source will take\n                        precedence. Values defined by an Env with a duplicate key\n                        will take precedence. Cannot be updated.\n                      items:\n                        description: EnvFromSource represents the source of a set\n                          of ConfigMaps\n                        properties:\n                          configMapRef:\n                            description: The ConfigMap to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap must be\n                                  defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          prefix:\n                            description: An optional identifier to prepend to each\n                              key in the ConfigMap. Must be a C_IDENTIFIER.\n                            type: string\n                          secretRef:\n                            description: The Secret to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret must be defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    image:\n                      description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                        This field is optional to allow higher level config management\n                        to default or override container images in workload controllers\n                        like Deployments and StatefulSets.'\n                      type: string\n                    imagePullPolicy:\n                      description: 'Image pull policy. One of Always, Never, IfNotPresent.\n                        Defaults to Always if :latest tag is specified, or IfNotPresent\n                        otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                      type: string\n                    lifecycle:\n                      description: Actions that the management system should take\n                        in response to container lifecycle events. Cannot be updated.\n                      properties:\n                        postStart:\n                          description: 'PostStart is called immediately after a container\n                            is created. If the handler fails, the container is terminated\n                            and restarted according to its restart policy. Other management\n                            of the container blocks until the hook completes. More\n                            info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                        preStop:\n                          description: 'PreStop is called immediately before a container\n                            is terminated due to an API request or management event\n                            such as liveness/startup probe failure, preemption, resource\n                            contention, etc. The handler is not called if the container\n                            crashes or exits. The Pod''s termination grace period\n                            countdown begins before the PreStop hook is executed.\n                            Regardless of the outcome of the handler, the container\n                            will eventually terminate within the Pod''s termination\n                            grace period (unless delayed by finalizers). Other management\n                            of the container blocks until the hook completes or until\n                            the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                      type: object\n                    livenessProbe:\n                      description: 'Periodic probe of container liveness. Container\n                        will be restarted if the probe fails. Cannot be updated. More\n                        info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    name:\n                      description: Name of the container specified as a DNS_LABEL.\n                        Each container in a pod must have a unique name (DNS_LABEL).\n                        Cannot be updated.\n                      type: string\n                    ports:\n                      description: List of ports to expose from the container. Not\n                        specifying a port here DOES NOT prevent that port from being\n                        exposed. Any port which is listening on the default \"0.0.0.0\"\n                        address inside a container will be accessible from the network.\n                        Modifying this array with strategic merge patch may corrupt\n                        the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255.\n                        Cannot be updated.\n                      items:\n                        description: ContainerPort represents a network port in a\n                          single container.\n                        properties:\n                          containerPort:\n                            description: Number of port to expose on the pod's IP\n                              address. This must be a valid port number, 0 < x < 65536.\n                            format: int32\n                            type: integer\n                          hostIP:\n                            description: What host IP to bind the external port to.\n                            type: string\n                          hostPort:\n                            description: Number of port to expose on the host. If\n                              specified, this must be a valid port number, 0 < x <\n                              65536. If HostNetwork is specified, this must match\n                              ContainerPort. Most containers do not need this.\n                            format: int32\n                            type: integer\n                          name:\n                            description: If specified, this must be an IANA_SVC_NAME\n                              and unique within the pod. Each named port in a pod\n                              must have a unique name. Name for the port that can\n                              be referred to by services.\n                            type: string\n                          protocol:\n                            default: TCP\n                            description: Protocol for port. Must be UDP, TCP, or SCTP.\n                              Defaults to \"TCP\".\n                            type: string\n                        required:\n                        - containerPort\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - containerPort\n                      - protocol\n                      x-kubernetes-list-type: map\n                    readinessProbe:\n                      description: 'Periodic probe of container service readiness.\n                        Container will be removed from service endpoints if the probe\n                        fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    resizePolicy:\n                      description: Resources resize policy for the container.\n                      items:\n                        description: ContainerResizePolicy represents resource resize\n                          policy for the container.\n                        properties:\n                          resourceName:\n                            description: 'Name of the resource to which this resource\n                              resize policy applies. Supported values: cpu, memory.'\n                            type: string\n                          restartPolicy:\n                            description: Restart policy to apply when specified resource\n                              is resized. If not specified, it defaults to NotRequired.\n                            type: string\n                        required:\n                        - resourceName\n                        - restartPolicy\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    resources:\n                      description: 'Compute Resources required by this container.\n                        Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                      properties:\n                        claims:\n                          description: \"Claims lists the names of resources, defined\n                            in spec.resourceClaims, that are used by this container.\n                            \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                            feature gate. \\n This field is immutable. It can only\n                            be set for containers.\"\n                          items:\n                            description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                            properties:\n                              name:\n                                description: Name must match the name of one entry\n                                  in pod.spec.resourceClaims of the Pod where this\n                                  field is used. It makes that resource available\n                                  inside a container.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - name\n                          x-kubernetes-list-type: map\n                        limits:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Limits describes the maximum amount of compute\n                            resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                        requests:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Requests describes the minimum amount of compute\n                            resources required. If Requests is omitted for a container,\n                            it defaults to Limits if that is explicitly specified,\n                            otherwise to an implementation-defined value. Requests\n                            cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                      type: object\n                    restartPolicy:\n                      description: 'RestartPolicy defines the restart behavior of\n                        individual containers in a pod. This field may only be set\n                        for init containers, and the only allowed value is \"Always\".\n                        For non-init containers or when this field is not specified,\n                        the restart behavior is defined by the Pod''s restart policy\n                        and the container type. Setting the RestartPolicy as \"Always\"\n                        for the init container will have the following effect: this\n                        init container will be continually restarted on exit until\n                        all regular containers have terminated. Once all regular containers\n                        have completed, all init containers with restartPolicy \"Always\"\n                        will be shut down. This lifecycle differs from normal init\n                        containers and is often referred to as a \"sidecar\" container.\n                        Although this init container still starts in the init container\n                        sequence, it does not wait for the container to complete before\n                        proceeding to the next init container. Instead, the next init\n                        container starts immediately after this init container is\n                        started, or after any startupProbe has successfully completed.'\n                      type: string\n                    securityContext:\n                      description: 'SecurityContext defines the security options the\n                        container should be run with. If set, the fields of SecurityContext\n                        override the equivalent fields of PodSecurityContext. More\n                        info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                      properties:\n                        allowPrivilegeEscalation:\n                          description: 'AllowPrivilegeEscalation controls whether\n                            a process can gain more privileges than its parent process.\n                            This bool directly controls if the no_new_privs flag will\n                            be set on the container process. AllowPrivilegeEscalation\n                            is true always when the container is: 1) run as Privileged\n                            2) has CAP_SYS_ADMIN Note that this field cannot be set\n                            when spec.os.name is windows.'\n                          type: boolean\n                        capabilities:\n                          description: The capabilities to add/drop when running containers.\n                            Defaults to the default set of capabilities granted by\n                            the container runtime. Note that this field cannot be\n                            set when spec.os.name is windows.\n                          properties:\n                            add:\n                              description: Added capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                            drop:\n                              description: Removed capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                          type: object\n                        privileged:\n                          description: Run container in privileged mode. Processes\n                            in privileged containers are essentially equivalent to\n                            root on the host. Defaults to false. Note that this field\n                            cannot be set when spec.os.name is windows.\n                          type: boolean\n                        procMount:\n                          description: procMount denotes the type of proc mount to\n                            use for the containers. The default is DefaultProcMount\n                            which uses the container runtime defaults for readonly\n                            paths and masked paths. This requires the ProcMountType\n                            feature flag to be enabled. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: string\n                        readOnlyRootFilesystem:\n                          description: Whether this container has a read-only root\n                            filesystem. Default is false. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: boolean\n                        runAsGroup:\n                          description: The GID to run the entrypoint of the container\n                            process. Uses runtime default if unset. May also be set\n                            in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        runAsNonRoot:\n                          description: Indicates that the container must run as a\n                            non-root user. If true, the Kubelet will validate the\n                            image at runtime to ensure that it does not run as UID\n                            0 (root) and fail to start the container if it does. If\n                            unset or false, no such validation will be performed.\n                            May also be set in PodSecurityContext.  If set in both\n                            SecurityContext and PodSecurityContext, the value specified\n                            in SecurityContext takes precedence.\n                          type: boolean\n                        runAsUser:\n                          description: The UID to run the entrypoint of the container\n                            process. Defaults to user specified in image metadata\n                            if unspecified. May also be set in PodSecurityContext.  If\n                            set in both SecurityContext and PodSecurityContext, the\n                            value specified in SecurityContext takes precedence. Note\n                            that this field cannot be set when spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        seLinuxOptions:\n                          description: The SELinux context to be applied to the container.\n                            If unspecified, the container runtime will allocate a\n                            random SELinux context for each container.  May also be\n                            set in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          properties:\n                            level:\n                              description: Level is SELinux level label that applies\n                                to the container.\n                              type: string\n                            role:\n                              description: Role is a SELinux role label that applies\n                                to the container.\n                              type: string\n                            type:\n                              description: Type is a SELinux type label that applies\n                                to the container.\n                              type: string\n                            user:\n                              description: User is a SELinux user label that applies\n                                to the container.\n                              type: string\n                          type: object\n                        seccompProfile:\n                          description: The seccomp options to use by this container.\n                            If seccomp options are provided at both the pod & container\n                            level, the container options override the pod options.\n                            Note that this field cannot be set when spec.os.name is\n                            windows.\n                          properties:\n                            localhostProfile:\n                              description: localhostProfile indicates a profile defined\n                                in a file on the node should be used. The profile\n                                must be preconfigured on the node to work. Must be\n                                a descending path, relative to the kubelet's configured\n                                seccomp profile location. Must be set if type is \"Localhost\".\n                                Must NOT be set for any other type.\n                              type: string\n                            type:\n                              description: \"type indicates which kind of seccomp profile\n                                will be applied. Valid options are: \\n Localhost -\n                                a profile defined in a file on the node should be\n                                used. RuntimeDefault - the container runtime default\n                                profile should be used. Unconfined - no profile should\n                                be applied.\"\n                              type: string\n                          required:\n                          - type\n                          type: object\n                        windowsOptions:\n                          description: The Windows specific settings applied to all\n                            containers. If unspecified, the options from the PodSecurityContext\n                            will be used. If set in both SecurityContext and PodSecurityContext,\n                            the value specified in SecurityContext takes precedence.\n                            Note that this field cannot be set when spec.os.name is\n                            linux.\n                          properties:\n                            gmsaCredentialSpec:\n                              description: GMSACredentialSpec is where the GMSA admission\n                                webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                inlines the contents of the GMSA credential spec named\n                                by the GMSACredentialSpecName field.\n                              type: string\n                            gmsaCredentialSpecName:\n                              description: GMSACredentialSpecName is the name of the\n                                GMSA credential spec to use.\n                              type: string\n                            hostProcess:\n                              description: HostProcess determines if a container should\n                                be run as a 'Host Process' container. All of a Pod's\n                                containers must have the same effective HostProcess\n                                value (it is not allowed to have a mix of HostProcess\n                                containers and non-HostProcess containers). In addition,\n                                if HostProcess is true then HostNetwork must also\n                                be set to true.\n                              type: boolean\n                            runAsUserName:\n                              description: The UserName in Windows to run the entrypoint\n                                of the container process. Defaults to the user specified\n                                in image metadata if unspecified. May also be set\n                                in PodSecurityContext. If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence.\n                              type: string\n                          type: object\n                      type: object\n                    startupProbe:\n                      description: 'StartupProbe indicates that the Pod has successfully\n                        initialized. If specified, no other probes are executed until\n                        this completes successfully. If this probe fails, the Pod\n                        will be restarted, just as if the livenessProbe failed. This\n                        can be used to provide different probe parameters at the beginning\n                        of a Pod''s lifecycle, when it might take a long time to load\n                        data or warm a cache, than during steady-state operation.\n                        This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    stdin:\n                      description: Whether this container should allocate a buffer\n                        for stdin in the container runtime. If this is not set, reads\n                        from stdin in the container will always result in EOF. Default\n                        is false.\n                      type: boolean\n                    stdinOnce:\n                      description: Whether the container runtime should close the\n                        stdin channel after it has been opened by a single attach.\n                        When stdin is true the stdin stream will remain open across\n                        multiple attach sessions. If stdinOnce is set to true, stdin\n                        is opened on container start, is empty until the first client\n                        attaches to stdin, and then remains open and accepts data\n                        until the client disconnects, at which time stdin is closed\n                        and remains closed until the container is restarted. If this\n                        flag is false, a container processes that reads from stdin\n                        will never receive an EOF. Default is false\n                      type: boolean\n                    terminationMessagePath:\n                      description: 'Optional: Path at which the file to which the\n                        container''s termination message will be written is mounted\n                        into the container''s filesystem. Message written is intended\n                        to be brief final status, such as an assertion failure message.\n                        Will be truncated by the node if greater than 4096 bytes.\n                        The total message length across all containers will be limited\n                        to 12kb. Defaults to /dev/termination-log. Cannot be updated.'\n                      type: string\n                    terminationMessagePolicy:\n                      description: Indicate how the termination message should be\n                        populated. File will use the contents of terminationMessagePath\n                        to populate the container status message on both success and\n                        failure. FallbackToLogsOnError will use the last chunk of\n                        container log output if the termination message file is empty\n                        and the container exited with an error. The log output is\n                        limited to 2048 bytes or 80 lines, whichever is smaller. Defaults\n                        to File. Cannot be updated.\n                      type: string\n                    tty:\n                      description: Whether this container should allocate a TTY for\n                        itself, also requires 'stdin' to be true. Default is false.\n                      type: boolean\n                    volumeDevices:\n                      description: volumeDevices is the list of block devices to be\n                        used by the container.\n                      items:\n                        description: volumeDevice describes a mapping of a raw block\n                          device within a container.\n                        properties:\n                          devicePath:\n                            description: devicePath is the path inside of the container\n                              that the device will be mapped to.\n                            type: string\n                          name:\n                            description: name must match the name of a persistentVolumeClaim\n                              in the pod\n                            type: string\n                        required:\n                        - devicePath\n                        - name\n                        type: object\n                      type: array\n                    volumeMounts:\n                      description: Pod volumes to mount into the container's filesystem.\n                        Cannot be updated.\n                      items:\n                        description: VolumeMount describes a mounting of a Volume\n                          within a container.\n                        properties:\n                          mountPath:\n                            description: Path within the container at which the volume\n                              should be mounted.  Must not contain ':'.\n                            type: string\n                          mountPropagation:\n                            description: mountPropagation determines how mounts are\n                              propagated from the host to container and the other\n                              way around. When not set, MountPropagationNone is used.\n                              This field is beta in 1.10.\n                            type: string\n                          name:\n                            description: This must match the Name of a Volume.\n                            type: string\n                          readOnly:\n                            description: Mounted read-only if true, read-write otherwise\n                              (false or unspecified). Defaults to false.\n                            type: boolean\n                          subPath:\n                            description: Path within the volume from which the container's\n                              volume should be mounted. Defaults to \"\" (volume's root).\n                            type: string\n                          subPathExpr:\n                            description: Expanded path within the volume from which\n                              the container's volume should be mounted. Behaves similarly\n                              to SubPath but environment variable references $(VAR_NAME)\n                              are expanded using the container's environment. Defaults\n                              to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                              exclusive.\n                            type: string\n                        required:\n                        - mountPath\n                        - name\n                        type: object\n                      type: array\n                    workingDir:\n                      description: Container's working directory. If not specified,\n                        the container runtime's default will be used, which might\n                        be configured in the container image. Cannot be updated.\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              enforcedNamespaceLabel:\n                description: EnforcedNamespaceLabel enforces adding a namespace label\n                  of origin for each alert and metric that is user created. The label\n                  value will always be the namespace of the object that is being created.\n                type: string\n              evaluationInterval:\n                default: 15s\n                description: Interval between consecutive evaluations.\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              excludedFromEnforcement:\n                description: List of references to PrometheusRule objects to be excluded\n                  from enforcing a namespace label of origin. Applies only if enforcedNamespaceLabel\n                  set to true.\n                items:\n                  description: ObjectReference references a PodMonitor, ServiceMonitor,\n                    Probe or PrometheusRule object.\n                  properties:\n                    group:\n                      default: monitoring.coreos.com\n                      description: Group of the referent. When not specified, it defaults\n                        to `monitoring.coreos.com`\n                      enum:\n                      - monitoring.coreos.com\n                      type: string\n                    name:\n                      description: Name of the referent. When not set, all resources\n                        in the namespace are matched.\n                      type: string\n                    namespace:\n                      description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'\n                      minLength: 1\n                      type: string\n                    resource:\n                      description: Resource of the referent.\n                      enum:\n                      - prometheusrules\n                      - servicemonitors\n                      - podmonitors\n                      - probes\n                      - scrapeconfigs\n                      type: string\n                  required:\n                  - namespace\n                  - resource\n                  type: object\n                type: array\n              externalPrefix:\n                description: The external URL the Thanos Ruler instances will be available\n                  under. This is necessary to generate correct URLs. This is necessary\n                  if Thanos Ruler is not served from root of a DNS name.\n                type: string\n              grpcServerTlsConfig:\n                description: 'GRPCServerTLSConfig configures the gRPC server from\n                  which Thanos Querier reads recorded rule data. Note: Currently only\n                  the CAFile, CertFile, and KeyFile fields are supported. Maps to\n                  the ''--grpc-server-tls-*'' CLI args.'\n                properties:\n                  ca:\n                    description: Certificate authority used when verifying server\n                      certificates.\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  caFile:\n                    description: Path to the CA cert in the Prometheus container to\n                      use for the targets.\n                    type: string\n                  cert:\n                    description: Client certificate to present when doing client-authentication.\n                    properties:\n                      configMap:\n                        description: ConfigMap containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key to select.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the ConfigMap or its key\n                              must be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      secret:\n                        description: Secret containing data to use for the targets.\n                        properties:\n                          key:\n                            description: The key of the secret to select from.  Must\n                              be a valid secret key.\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                              TODO: Add other useful fields. apiVersion, kind, uid?'\n                            type: string\n                          optional:\n                            description: Specify whether the Secret or its key must\n                              be defined\n                            type: boolean\n                        required:\n                        - key\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  certFile:\n                    description: Path to the client cert file in the Prometheus container\n                      for the targets.\n                    type: string\n                  insecureSkipVerify:\n                    description: Disable target certificate validation.\n                    type: boolean\n                  keyFile:\n                    description: Path to the client key file in the Prometheus container\n                      for the targets.\n                    type: string\n                  keySecret:\n                    description: Secret containing the client key file for the targets.\n                    properties:\n                      key:\n                        description: The key of the secret to select from.  Must be\n                          a valid secret key.\n                        type: string\n                      name:\n                        description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                          TODO: Add other useful fields. apiVersion, kind, uid?'\n                        type: string\n                      optional:\n                        description: Specify whether the Secret or its key must be\n                          defined\n                        type: boolean\n                    required:\n                    - key\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  serverName:\n                    description: Used to verify the hostname for the targets.\n                    type: string\n                type: object\n              hostAliases:\n                description: Pods' hostAliases configuration\n                items:\n                  description: HostAlias holds the mapping between IP and hostnames\n                    that will be injected as an entry in the pod's hosts file.\n                  properties:\n                    hostnames:\n                      description: Hostnames for the above IP address.\n                      items:\n                        type: string\n                      type: array\n                    ip:\n                      description: IP address of the host file entry.\n                      type: string\n                  required:\n                  - hostnames\n                  - ip\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - ip\n                x-kubernetes-list-type: map\n              image:\n                description: Thanos container image URL.\n                type: string\n              imagePullPolicy:\n                description: Image pull policy for the 'thanos', 'init-config-reloader'\n                  and 'config-reloader' containers. See https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy\n                  for more details.\n                enum:\n                - \"\"\n                - Always\n                - Never\n                - IfNotPresent\n                type: string\n              imagePullSecrets:\n                description: An optional list of references to secrets in the same\n                  namespace to use for pulling thanos images from registries see http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod\n                items:\n                  description: LocalObjectReference contains enough information to\n                    let you locate the referenced object inside the same namespace.\n                  properties:\n                    name:\n                      description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                        TODO: Add other useful fields. apiVersion, kind, uid?'\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                type: array\n              initContainers:\n                description: 'InitContainers allows adding initContainers to the pod\n                  definition. Those can be used to e.g. fetch secrets for injection\n                  into the ThanosRuler configuration from external sources. Any errors\n                  during the execution of an initContainer will lead to a restart\n                  of the Pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/\n                  Using initContainers for any use case other then secret fetching\n                  is entirely outside the scope of what the maintainers will support\n                  and by doing so, you accept that this behaviour may break at any\n                  time without notice.'\n                items:\n                  description: A single application container that you want to run\n                    within a pod.\n                  properties:\n                    args:\n                      description: 'Arguments to the entrypoint. The container image''s\n                        CMD is used if this is not provided. Variable references $(VAR_NAME)\n                        are expanded using the container''s environment. If a variable\n                        cannot be resolved, the reference in the input string will\n                        be unchanged. Double $$ are reduced to a single $, which allows\n                        for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\n                        produce the string literal \"$(VAR_NAME)\". Escaped references\n                        will never be expanded, regardless of whether the variable\n                        exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    command:\n                      description: 'Entrypoint array. Not executed within a shell.\n                        The container image''s ENTRYPOINT is used if this is not provided.\n                        Variable references $(VAR_NAME) are expanded using the container''s\n                        environment. If a variable cannot be resolved, the reference\n                        in the input string will be unchanged. Double $$ are reduced\n                        to a single $, which allows for escaping the $(VAR_NAME) syntax:\n                        i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\".\n                        Escaped references will never be expanded, regardless of whether\n                        the variable exists or not. Cannot be updated. More info:\n                        https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                      items:\n                        type: string\n                      type: array\n                    env:\n                      description: List of environment variables to set in the container.\n                        Cannot be updated.\n                      items:\n                        description: EnvVar represents an environment variable present\n                          in a Container.\n                        properties:\n                          name:\n                            description: Name of the environment variable. Must be\n                              a C_IDENTIFIER.\n                            type: string\n                          value:\n                            description: 'Variable references $(VAR_NAME) are expanded\n                              using the previously defined environment variables in\n                              the container and any service environment variables.\n                              If a variable cannot be resolved, the reference in the\n                              input string will be unchanged. Double $$ are reduced\n                              to a single $, which allows for escaping the $(VAR_NAME)\n                              syntax: i.e. \"$$(VAR_NAME)\" will produce the string\n                              literal \"$(VAR_NAME)\". Escaped references will never\n                              be expanded, regardless of whether the variable exists\n                              or not. Defaults to \"\".'\n                            type: string\n                          valueFrom:\n                            description: Source for the environment variable's value.\n                              Cannot be used if value is not empty.\n                            properties:\n                              configMapKeyRef:\n                                description: Selects a key of a ConfigMap.\n                                properties:\n                                  key:\n                                    description: The key to select.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              fieldRef:\n                                description: 'Selects a field of the pod: supports\n                                  metadata.name, metadata.namespace, `metadata.labels[''<KEY>'']`,\n                                  `metadata.annotations[''<KEY>'']`, spec.nodeName,\n                                  spec.serviceAccountName, status.hostIP, status.podIP,\n                                  status.podIPs.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, limits.ephemeral-storage, requests.cpu,\n                                  requests.memory and requests.ephemeral-storage)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              secretKeyRef:\n                                description: Selects a key of a secret in the pod's\n                                  namespace\n                                properties:\n                                  key:\n                                    description: The key of the secret to select from.  Must\n                                      be a valid secret key.\n                                    type: string\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      key must be defined\n                                    type: boolean\n                                required:\n                                - key\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                        required:\n                        - name\n                        type: object\n                      type: array\n                    envFrom:\n                      description: List of sources to populate environment variables\n                        in the container. The keys defined within a source must be\n                        a C_IDENTIFIER. All invalid keys will be reported as an event\n                        when the container is starting. When a key exists in multiple\n                        sources, the value associated with the last source will take\n                        precedence. Values defined by an Env with a duplicate key\n                        will take precedence. Cannot be updated.\n                      items:\n                        description: EnvFromSource represents the source of a set\n                          of ConfigMaps\n                        properties:\n                          configMapRef:\n                            description: The ConfigMap to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the ConfigMap must be\n                                  defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          prefix:\n                            description: An optional identifier to prepend to each\n                              key in the ConfigMap. Must be a C_IDENTIFIER.\n                            type: string\n                          secretRef:\n                            description: The Secret to select from\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                              optional:\n                                description: Specify whether the Secret must be defined\n                                type: boolean\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      type: array\n                    image:\n                      description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                        This field is optional to allow higher level config management\n                        to default or override container images in workload controllers\n                        like Deployments and StatefulSets.'\n                      type: string\n                    imagePullPolicy:\n                      description: 'Image pull policy. One of Always, Never, IfNotPresent.\n                        Defaults to Always if :latest tag is specified, or IfNotPresent\n                        otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                      type: string\n                    lifecycle:\n                      description: Actions that the management system should take\n                        in response to container lifecycle events. Cannot be updated.\n                      properties:\n                        postStart:\n                          description: 'PostStart is called immediately after a container\n                            is created. If the handler fails, the container is terminated\n                            and restarted according to its restart policy. Other management\n                            of the container blocks until the hook completes. More\n                            info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                        preStop:\n                          description: 'PreStop is called immediately before a container\n                            is terminated due to an API request or management event\n                            such as liveness/startup probe failure, preemption, resource\n                            contention, etc. The handler is not called if the container\n                            crashes or exits. The Pod''s termination grace period\n                            countdown begins before the PreStop hook is executed.\n                            Regardless of the outcome of the handler, the container\n                            will eventually terminate within the Pod''s termination\n                            grace period (unless delayed by finalizers). Other management\n                            of the container blocks until the hook completes or until\n                            the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                          properties:\n                            exec:\n                              description: Exec specifies the action to take.\n                              properties:\n                                command:\n                                  description: Command is the command line to execute\n                                    inside the container, the working directory for\n                                    the command  is root ('/') in the container's\n                                    filesystem. The command is simply exec'd, it is\n                                    not run inside a shell, so traditional shell instructions\n                                    ('|', etc) won't work. To use a shell, you need\n                                    to explicitly call out to that shell. Exit status\n                                    of 0 is treated as live/healthy and non-zero is\n                                    unhealthy.\n                                  items:\n                                    type: string\n                                  type: array\n                              type: object\n                            httpGet:\n                              description: HTTPGet specifies the http request to perform.\n                              properties:\n                                host:\n                                  description: Host name to connect to, defaults to\n                                    the pod IP. You probably want to set \"Host\" in\n                                    httpHeaders instead.\n                                  type: string\n                                httpHeaders:\n                                  description: Custom headers to set in the request.\n                                    HTTP allows repeated headers.\n                                  items:\n                                    description: HTTPHeader describes a custom header\n                                      to be used in HTTP probes\n                                    properties:\n                                      name:\n                                        description: The header field name. This will\n                                          be canonicalized upon output, so case-variant\n                                          names will be understood as the same header.\n                                        type: string\n                                      value:\n                                        description: The header field value\n                                        type: string\n                                    required:\n                                    - name\n                                    - value\n                                    type: object\n                                  type: array\n                                path:\n                                  description: Path to access on the HTTP server.\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Name or number of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                                scheme:\n                                  description: Scheme to use for connecting to the\n                                    host. Defaults to HTTP.\n                                  type: string\n                              required:\n                              - port\n                              type: object\n                            tcpSocket:\n                              description: Deprecated. TCPSocket is NOT supported\n                                as a LifecycleHandler and kept for the backward compatibility.\n                                There are no validation of this field and lifecycle\n                                hooks will fail in runtime when tcp handler is specified.\n                              properties:\n                                host:\n                                  description: 'Optional: Host name to connect to,\n                                    defaults to the pod IP.'\n                                  type: string\n                                port:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  description: Number or name of the port to access\n                                    on the container. Number must be in the range\n                                    1 to 65535. Name must be an IANA_SVC_NAME.\n                                  x-kubernetes-int-or-string: true\n                              required:\n                              - port\n                              type: object\n                          type: object\n                      type: object\n                    livenessProbe:\n                      description: 'Periodic probe of container liveness. Container\n                        will be restarted if the probe fails. Cannot be updated. More\n                        info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    name:\n                      description: Name of the container specified as a DNS_LABEL.\n                        Each container in a pod must have a unique name (DNS_LABEL).\n                        Cannot be updated.\n                      type: string\n                    ports:\n                      description: List of ports to expose from the container. Not\n                        specifying a port here DOES NOT prevent that port from being\n                        exposed. Any port which is listening on the default \"0.0.0.0\"\n                        address inside a container will be accessible from the network.\n                        Modifying this array with strategic merge patch may corrupt\n                        the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255.\n                        Cannot be updated.\n                      items:\n                        description: ContainerPort represents a network port in a\n                          single container.\n                        properties:\n                          containerPort:\n                            description: Number of port to expose on the pod's IP\n                              address. This must be a valid port number, 0 < x < 65536.\n                            format: int32\n                            type: integer\n                          hostIP:\n                            description: What host IP to bind the external port to.\n                            type: string\n                          hostPort:\n                            description: Number of port to expose on the host. If\n                              specified, this must be a valid port number, 0 < x <\n                              65536. If HostNetwork is specified, this must match\n                              ContainerPort. Most containers do not need this.\n                            format: int32\n                            type: integer\n                          name:\n                            description: If specified, this must be an IANA_SVC_NAME\n                              and unique within the pod. Each named port in a pod\n                              must have a unique name. Name for the port that can\n                              be referred to by services.\n                            type: string\n                          protocol:\n                            default: TCP\n                            description: Protocol for port. Must be UDP, TCP, or SCTP.\n                              Defaults to \"TCP\".\n                            type: string\n                        required:\n                        - containerPort\n                        type: object\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - containerPort\n                      - protocol\n                      x-kubernetes-list-type: map\n                    readinessProbe:\n                      description: 'Periodic probe of container service readiness.\n                        Container will be removed from service endpoints if the probe\n                        fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    resizePolicy:\n                      description: Resources resize policy for the container.\n                      items:\n                        description: ContainerResizePolicy represents resource resize\n                          policy for the container.\n                        properties:\n                          resourceName:\n                            description: 'Name of the resource to which this resource\n                              resize policy applies. Supported values: cpu, memory.'\n                            type: string\n                          restartPolicy:\n                            description: Restart policy to apply when specified resource\n                              is resized. If not specified, it defaults to NotRequired.\n                            type: string\n                        required:\n                        - resourceName\n                        - restartPolicy\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    resources:\n                      description: 'Compute Resources required by this container.\n                        Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                      properties:\n                        claims:\n                          description: \"Claims lists the names of resources, defined\n                            in spec.resourceClaims, that are used by this container.\n                            \\n This is an alpha field and requires enabling the DynamicResourceAllocation\n                            feature gate. \\n This field is immutable. It can only\n                            be set for containers.\"\n                          items:\n                            description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                            properties:\n                              name:\n                                description: Name must match the name of one entry\n                                  in pod.spec.resourceClaims of the Pod where this\n                                  field is used. It makes that resource available\n                                  inside a container.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - name\n                          x-kubernetes-list-type: map\n                        limits:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Limits describes the maximum amount of compute\n                            resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                        requests:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Requests describes the minimum amount of compute\n                            resources required. If Requests is omitted for a container,\n                            it defaults to Limits if that is explicitly specified,\n                            otherwise to an implementation-defined value. Requests\n                            cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                          type: object\n                      type: object\n                    restartPolicy:\n                      description: 'RestartPolicy defines the restart behavior of\n                        individual containers in a pod. This field may only be set\n                        for init containers, and the only allowed value is \"Always\".\n                        For non-init containers or when this field is not specified,\n                        the restart behavior is defined by the Pod''s restart policy\n                        and the container type. Setting the RestartPolicy as \"Always\"\n                        for the init container will have the following effect: this\n                        init container will be continually restarted on exit until\n                        all regular containers have terminated. Once all regular containers\n                        have completed, all init containers with restartPolicy \"Always\"\n                        will be shut down. This lifecycle differs from normal init\n                        containers and is often referred to as a \"sidecar\" container.\n                        Although this init container still starts in the init container\n                        sequence, it does not wait for the container to complete before\n                        proceeding to the next init container. Instead, the next init\n                        container starts immediately after this init container is\n                        started, or after any startupProbe has successfully completed.'\n                      type: string\n                    securityContext:\n                      description: 'SecurityContext defines the security options the\n                        container should be run with. If set, the fields of SecurityContext\n                        override the equivalent fields of PodSecurityContext. More\n                        info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                      properties:\n                        allowPrivilegeEscalation:\n                          description: 'AllowPrivilegeEscalation controls whether\n                            a process can gain more privileges than its parent process.\n                            This bool directly controls if the no_new_privs flag will\n                            be set on the container process. AllowPrivilegeEscalation\n                            is true always when the container is: 1) run as Privileged\n                            2) has CAP_SYS_ADMIN Note that this field cannot be set\n                            when spec.os.name is windows.'\n                          type: boolean\n                        capabilities:\n                          description: The capabilities to add/drop when running containers.\n                            Defaults to the default set of capabilities granted by\n                            the container runtime. Note that this field cannot be\n                            set when spec.os.name is windows.\n                          properties:\n                            add:\n                              description: Added capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                            drop:\n                              description: Removed capabilities\n                              items:\n                                description: Capability represent POSIX capabilities\n                                  type\n                                type: string\n                              type: array\n                          type: object\n                        privileged:\n                          description: Run container in privileged mode. Processes\n                            in privileged containers are essentially equivalent to\n                            root on the host. Defaults to false. Note that this field\n                            cannot be set when spec.os.name is windows.\n                          type: boolean\n                        procMount:\n                          description: procMount denotes the type of proc mount to\n                            use for the containers. The default is DefaultProcMount\n                            which uses the container runtime defaults for readonly\n                            paths and masked paths. This requires the ProcMountType\n                            feature flag to be enabled. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: string\n                        readOnlyRootFilesystem:\n                          description: Whether this container has a read-only root\n                            filesystem. Default is false. Note that this field cannot\n                            be set when spec.os.name is windows.\n                          type: boolean\n                        runAsGroup:\n                          description: The GID to run the entrypoint of the container\n                            process. Uses runtime default if unset. May also be set\n                            in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        runAsNonRoot:\n                          description: Indicates that the container must run as a\n                            non-root user. If true, the Kubelet will validate the\n                            image at runtime to ensure that it does not run as UID\n                            0 (root) and fail to start the container if it does. If\n                            unset or false, no such validation will be performed.\n                            May also be set in PodSecurityContext.  If set in both\n                            SecurityContext and PodSecurityContext, the value specified\n                            in SecurityContext takes precedence.\n                          type: boolean\n                        runAsUser:\n                          description: The UID to run the entrypoint of the container\n                            process. Defaults to user specified in image metadata\n                            if unspecified. May also be set in PodSecurityContext.  If\n                            set in both SecurityContext and PodSecurityContext, the\n                            value specified in SecurityContext takes precedence. Note\n                            that this field cannot be set when spec.os.name is windows.\n                          format: int64\n                          type: integer\n                        seLinuxOptions:\n                          description: The SELinux context to be applied to the container.\n                            If unspecified, the container runtime will allocate a\n                            random SELinux context for each container.  May also be\n                            set in PodSecurityContext.  If set in both SecurityContext\n                            and PodSecurityContext, the value specified in SecurityContext\n                            takes precedence. Note that this field cannot be set when\n                            spec.os.name is windows.\n                          properties:\n                            level:\n                              description: Level is SELinux level label that applies\n                                to the container.\n                              type: string\n                            role:\n                              description: Role is a SELinux role label that applies\n                                to the container.\n                              type: string\n                            type:\n                              description: Type is a SELinux type label that applies\n                                to the container.\n                              type: string\n                            user:\n                              description: User is a SELinux user label that applies\n                                to the container.\n                              type: string\n                          type: object\n                        seccompProfile:\n                          description: The seccomp options to use by this container.\n                            If seccomp options are provided at both the pod & container\n                            level, the container options override the pod options.\n                            Note that this field cannot be set when spec.os.name is\n                            windows.\n                          properties:\n                            localhostProfile:\n                              description: localhostProfile indicates a profile defined\n                                in a file on the node should be used. The profile\n                                must be preconfigured on the node to work. Must be\n                                a descending path, relative to the kubelet's configured\n                                seccomp profile location. Must be set if type is \"Localhost\".\n                                Must NOT be set for any other type.\n                              type: string\n                            type:\n                              description: \"type indicates which kind of seccomp profile\n                                will be applied. Valid options are: \\n Localhost -\n                                a profile defined in a file on the node should be\n                                used. RuntimeDefault - the container runtime default\n                                profile should be used. Unconfined - no profile should\n                                be applied.\"\n                              type: string\n                          required:\n                          - type\n                          type: object\n                        windowsOptions:\n                          description: The Windows specific settings applied to all\n                            containers. If unspecified, the options from the PodSecurityContext\n                            will be used. If set in both SecurityContext and PodSecurityContext,\n                            the value specified in SecurityContext takes precedence.\n                            Note that this field cannot be set when spec.os.name is\n                            linux.\n                          properties:\n                            gmsaCredentialSpec:\n                              description: GMSACredentialSpec is where the GMSA admission\n                                webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                inlines the contents of the GMSA credential spec named\n                                by the GMSACredentialSpecName field.\n                              type: string\n                            gmsaCredentialSpecName:\n                              description: GMSACredentialSpecName is the name of the\n                                GMSA credential spec to use.\n                              type: string\n                            hostProcess:\n                              description: HostProcess determines if a container should\n                                be run as a 'Host Process' container. All of a Pod's\n                                containers must have the same effective HostProcess\n                                value (it is not allowed to have a mix of HostProcess\n                                containers and non-HostProcess containers). In addition,\n                                if HostProcess is true then HostNetwork must also\n                                be set to true.\n                              type: boolean\n                            runAsUserName:\n                              description: The UserName in Windows to run the entrypoint\n                                of the container process. Defaults to the user specified\n                                in image metadata if unspecified. May also be set\n                                in PodSecurityContext. If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence.\n                              type: string\n                          type: object\n                      type: object\n                    startupProbe:\n                      description: 'StartupProbe indicates that the Pod has successfully\n                        initialized. If specified, no other probes are executed until\n                        this completes successfully. If this probe fails, the Pod\n                        will be restarted, just as if the livenessProbe failed. This\n                        can be used to provide different probe parameters at the beginning\n                        of a Pod''s lifecycle, when it might take a long time to load\n                        data or warm a cache, than during steady-state operation.\n                        This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                      properties:\n                        exec:\n                          description: Exec specifies the action to take.\n                          properties:\n                            command:\n                              description: Command is the command line to execute\n                                inside the container, the working directory for the\n                                command  is root ('/') in the container's filesystem.\n                                The command is simply exec'd, it is not run inside\n                                a shell, so traditional shell instructions ('|', etc)\n                                won't work. To use a shell, you need to explicitly\n                                call out to that shell. Exit status of 0 is treated\n                                as live/healthy and non-zero is unhealthy.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        failureThreshold:\n                          description: Minimum consecutive failures for the probe\n                            to be considered failed after having succeeded. Defaults\n                            to 3. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        grpc:\n                          description: GRPC specifies an action involving a GRPC port.\n                          properties:\n                            port:\n                              description: Port number of the gRPC service. Number\n                                must be in the range 1 to 65535.\n                              format: int32\n                              type: integer\n                            service:\n                              description: \"Service is the name of the service to\n                                place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n                                \\n If this is not specified, the default behavior\n                                is defined by gRPC.\"\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        httpGet:\n                          description: HTTPGet specifies the http request to perform.\n                          properties:\n                            host:\n                              description: Host name to connect to, defaults to the\n                                pod IP. You probably want to set \"Host\" in httpHeaders\n                                instead.\n                              type: string\n                            httpHeaders:\n                              description: Custom headers to set in the request. HTTP\n                                allows repeated headers.\n                              items:\n                                description: HTTPHeader describes a custom header\n                                  to be used in HTTP probes\n                                properties:\n                                  name:\n                                    description: The header field name. This will\n                                      be canonicalized upon output, so case-variant\n                                      names will be understood as the same header.\n                                    type: string\n                                  value:\n                                    description: The header field value\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            path:\n                              description: Path to access on the HTTP server.\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Name or number of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                            scheme:\n                              description: Scheme to use for connecting to the host.\n                                Defaults to HTTP.\n                              type: string\n                          required:\n                          - port\n                          type: object\n                        initialDelaySeconds:\n                          description: 'Number of seconds after the container has\n                            started before liveness probes are initiated. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                        periodSeconds:\n                          description: How often (in seconds) to perform the probe.\n                            Default to 10 seconds. Minimum value is 1.\n                          format: int32\n                          type: integer\n                        successThreshold:\n                          description: Minimum consecutive successes for the probe\n                            to be considered successful after having failed. Defaults\n                            to 1. Must be 1 for liveness and startup. Minimum value\n                            is 1.\n                          format: int32\n                          type: integer\n                        tcpSocket:\n                          description: TCPSocket specifies an action involving a TCP\n                            port.\n                          properties:\n                            host:\n                              description: 'Optional: Host name to connect to, defaults\n                                to the pod IP.'\n                              type: string\n                            port:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              description: Number or name of the port to access on\n                                the container. Number must be in the range 1 to 65535.\n                                Name must be an IANA_SVC_NAME.\n                              x-kubernetes-int-or-string: true\n                          required:\n                          - port\n                          type: object\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully upon probe failure. The grace\n                            period is the duration in seconds after the processes\n                            running in the pod are sent a termination signal and the\n                            time when the processes are forcibly halted with a kill\n                            signal. Set this value longer than the expected cleanup\n                            time for your process. If this value is nil, the pod's\n                            terminationGracePeriodSeconds will be used. Otherwise,\n                            this value overrides the value provided by the pod spec.\n                            Value must be non-negative integer. The value zero indicates\n                            stop immediately via the kill signal (no opportunity to\n                            shut down). This is a beta field and requires enabling\n                            ProbeTerminationGracePeriod feature gate. Minimum value\n                            is 1. spec.terminationGracePeriodSeconds is used if unset.\n                          format: int64\n                          type: integer\n                        timeoutSeconds:\n                          description: 'Number of seconds after which the probe times\n                            out. Defaults to 1 second. Minimum value is 1. More info:\n                            https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                          format: int32\n                          type: integer\n                      type: object\n                    stdin:\n                      description: Whether this container should allocate a buffer\n                        for stdin in the container runtime. If this is not set, reads\n                        from stdin in the container will always result in EOF. Default\n                        is false.\n                      type: boolean\n                    stdinOnce:\n                      description: Whether the container runtime should close the\n                        stdin channel after it has been opened by a single attach.\n                        When stdin is true the stdin stream will remain open across\n                        multiple attach sessions. If stdinOnce is set to true, stdin\n                        is opened on container start, is empty until the first client\n                        attaches to stdin, and then remains open and accepts data\n                        until the client disconnects, at which time stdin is closed\n                        and remains closed until the container is restarted. If this\n                        flag is false, a container processes that reads from stdin\n                        will never receive an EOF. Default is false\n                      type: boolean\n                    terminationMessagePath:\n                      description: 'Optional: Path at which the file to which the\n                        container''s termination message will be written is mounted\n                        into the container''s filesystem. Message written is intended\n                        to be brief final status, such as an assertion failure message.\n                        Will be truncated by the node if greater than 4096 bytes.\n                        The total message length across all containers will be limited\n                        to 12kb. Defaults to /dev/termination-log. Cannot be updated.'\n                      type: string\n                    terminationMessagePolicy:\n                      description: Indicate how the termination message should be\n                        populated. File will use the contents of terminationMessagePath\n                        to populate the container status message on both success and\n                        failure. FallbackToLogsOnError will use the last chunk of\n                        container log output if the termination message file is empty\n                        and the container exited with an error. The log output is\n                        limited to 2048 bytes or 80 lines, whichever is smaller. Defaults\n                        to File. Cannot be updated.\n                      type: string\n                    tty:\n                      description: Whether this container should allocate a TTY for\n                        itself, also requires 'stdin' to be true. Default is false.\n                      type: boolean\n                    volumeDevices:\n                      description: volumeDevices is the list of block devices to be\n                        used by the container.\n                      items:\n                        description: volumeDevice describes a mapping of a raw block\n                          device within a container.\n                        properties:\n                          devicePath:\n                            description: devicePath is the path inside of the container\n                              that the device will be mapped to.\n                            type: string\n                          name:\n                            description: name must match the name of a persistentVolumeClaim\n                              in the pod\n                            type: string\n                        required:\n                        - devicePath\n                        - name\n                        type: object\n                      type: array\n                    volumeMounts:\n                      description: Pod volumes to mount into the container's filesystem.\n                        Cannot be updated.\n                      items:\n                        description: VolumeMount describes a mounting of a Volume\n                          within a container.\n                        properties:\n                          mountPath:\n                            description: Path within the container at which the volume\n                              should be mounted.  Must not contain ':'.\n                            type: string\n                          mountPropagation:\n                            description: mountPropagation determines how mounts are\n                              propagated from the host to container and the other\n                              way around. When not set, MountPropagationNone is used.\n                              This field is beta in 1.10.\n                            type: string\n                          name:\n                            description: This must match the Name of a Volume.\n                            type: string\n                          readOnly:\n                            description: Mounted read-only if true, read-write otherwise\n                              (false or unspecified). Defaults to false.\n                            type: boolean\n                          subPath:\n                            description: Path within the volume from which the container's\n                              volume should be mounted. Defaults to \"\" (volume's root).\n                            type: string\n                          subPathExpr:\n                            description: Expanded path within the volume from which\n                              the container's volume should be mounted. Behaves similarly\n                              to SubPath but environment variable references $(VAR_NAME)\n                              are expanded using the container's environment. Defaults\n                              to \"\" (volume's root). SubPathExpr and SubPath are mutually\n                              exclusive.\n                            type: string\n                        required:\n                        - mountPath\n                        - name\n                        type: object\n                      type: array\n                    workingDir:\n                      description: Container's working directory. If not specified,\n                        the container runtime's default will be used, which might\n                        be configured in the container image. Cannot be updated.\n                      type: string\n                  required:\n                  - name\n                  type: object\n                type: array\n              labels:\n                additionalProperties:\n                  type: string\n                description: Labels configure the external label pairs to ThanosRuler.\n                  A default replica label `thanos_ruler_replica` will be always added  as\n                  a label with the value of the pod's name and it will be dropped\n                  in the alerts.\n                type: object\n              listenLocal:\n                description: ListenLocal makes the Thanos ruler listen on loopback,\n                  so that it does not bind against the Pod IP.\n                type: boolean\n              logFormat:\n                description: Log format for ThanosRuler to be configured with.\n                enum:\n                - \"\"\n                - logfmt\n                - json\n                type: string\n              logLevel:\n                description: Log level for ThanosRuler to be configured with.\n                enum:\n                - \"\"\n                - debug\n                - info\n                - warn\n                - error\n                type: string\n              minReadySeconds:\n                description: Minimum number of seconds for which a newly created pod\n                  should be ready without any of its container crashing for it to\n                  be considered available. Defaults to 0 (pod will be considered available\n                  as soon as it is ready) This is an alpha field from kubernetes 1.22\n                  until 1.24 which requires enabling the StatefulSetMinReadySeconds\n                  feature gate.\n                format: int32\n                type: integer\n              nodeSelector:\n                additionalProperties:\n                  type: string\n                description: Define which Nodes the Pods are scheduled on.\n                type: object\n              objectStorageConfig:\n                description: ObjectStorageConfig configures object storage in Thanos.\n                  Alternative to ObjectStorageConfigFile, and lower order priority.\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              objectStorageConfigFile:\n                description: ObjectStorageConfigFile specifies the path of the object\n                  storage configuration file. When used alongside with ObjectStorageConfig,\n                  ObjectStorageConfigFile takes precedence.\n                type: string\n              paused:\n                description: When a ThanosRuler deployment is paused, no actions except\n                  for deletion will be performed on the underlying objects.\n                type: boolean\n              podMetadata:\n                description: \"PodMetadata configures labels and annotations which\n                  are propagated to the ThanosRuler pods. \\n The following items are\n                  reserved and cannot be overridden: * \\\"app.kubernetes.io/name\\\"\n                  label, set to \\\"thanos-ruler\\\". * \\\"app.kubernetes.io/managed-by\\\"\n                  label, set to \\\"prometheus-operator\\\". * \\\"app.kubernetes.io/instance\\\"\n                  label, set to the name of the ThanosRuler instance. * \\\"thanos-ruler\\\"\n                  label, set to the name of the ThanosRuler instance. * \\\"kubectl.kubernetes.io/default-container\\\"\n                  annotation, set to \\\"thanos-ruler\\\".\"\n                properties:\n                  annotations:\n                    additionalProperties:\n                      type: string\n                    description: 'Annotations is an unstructured key value map stored\n                      with a resource that may be set by external tools to store and\n                      retrieve arbitrary metadata. They are not queryable and should\n                      be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'\n                    type: object\n                  labels:\n                    additionalProperties:\n                      type: string\n                    description: 'Map of string keys and values that can be used to\n                      organize and categorize (scope and select) objects. May match\n                      selectors of replication controllers and services. More info:\n                      http://kubernetes.io/docs/user-guide/labels'\n                    type: object\n                  name:\n                    description: 'Name must be unique within a namespace. Is required\n                      when creating resources, although some resources may allow a\n                      client to request the generation of an appropriate name automatically.\n                      Name is primarily intended for creation idempotence and configuration\n                      definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'\n                    type: string\n                type: object\n              portName:\n                default: web\n                description: Port name used for the pods and governing service. Defaults\n                  to `web`.\n                type: string\n              priorityClassName:\n                description: Priority class assigned to the Pods\n                type: string\n              prometheusRulesExcludedFromEnforce:\n                description: 'PrometheusRulesExcludedFromEnforce - list of Prometheus\n                  rules to be excluded from enforcing of adding namespace labels.\n                  Works only if enforcedNamespaceLabel set to true. Make sure both\n                  ruleNamespace and ruleName are set for each pair Deprecated: use\n                  excludedFromEnforcement instead.'\n                items:\n                  description: PrometheusRuleExcludeConfig enables users to configure\n                    excluded PrometheusRule names and their namespaces to be ignored\n                    while enforcing namespace label for alerts and metrics.\n                  properties:\n                    ruleName:\n                      description: Name of the excluded PrometheusRule object.\n                      type: string\n                    ruleNamespace:\n                      description: Namespace of the excluded PrometheusRule object.\n                      type: string\n                  required:\n                  - ruleName\n                  - ruleNamespace\n                  type: object\n                type: array\n              queryConfig:\n                description: Define configuration for connecting to thanos query instances.\n                  If this is defined, the QueryEndpoints field will be ignored. Maps\n                  to the `query.config` CLI argument. Only available with thanos v0.11.0\n                  and higher.\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              queryEndpoints:\n                description: QueryEndpoints defines Thanos querier endpoints from\n                  which to query metrics. Maps to the --query flag of thanos ruler.\n                items:\n                  type: string\n                type: array\n              replicas:\n                description: Number of thanos ruler instances to deploy.\n                format: int32\n                type: integer\n              resources:\n                description: Resources defines the resource requirements for single\n                  Pods. If not provided, no requests/limits will be set\n                properties:\n                  claims:\n                    description: \"Claims lists the names of resources, defined in\n                      spec.resourceClaims, that are used by this container. \\n This\n                      is an alpha field and requires enabling the DynamicResourceAllocation\n                      feature gate. \\n This field is immutable. It can only be set\n                      for containers.\"\n                    items:\n                      description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                      properties:\n                        name:\n                          description: Name must match the name of one entry in pod.spec.resourceClaims\n                            of the Pod where this field is used. It makes that resource\n                            available inside a container.\n                          type: string\n                      required:\n                      - name\n                      type: object\n                    type: array\n                    x-kubernetes-list-map-keys:\n                    - name\n                    x-kubernetes-list-type: map\n                  limits:\n                    additionalProperties:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                      x-kubernetes-int-or-string: true\n                    description: 'Limits describes the maximum amount of compute resources\n                      allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                    type: object\n                  requests:\n                    additionalProperties:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                      x-kubernetes-int-or-string: true\n                    description: 'Requests describes the minimum amount of compute\n                      resources required. If Requests is omitted for a container,\n                      it defaults to Limits if that is explicitly specified, otherwise\n                      to an implementation-defined value. Requests cannot exceed Limits.\n                      More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                    type: object\n                type: object\n              retention:\n                default: 24h\n                description: Time duration ThanosRuler shall retain data for. Default\n                  is '24h', and must match the regular expression `[0-9]+(ms|s|m|h|d|w|y)`\n                  (milliseconds seconds minutes hours days weeks years).\n                pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$\n                type: string\n              routePrefix:\n                description: The route prefix ThanosRuler registers HTTP handlers\n                  for. This allows thanos UI to be served on a sub-path.\n                type: string\n              ruleNamespaceSelector:\n                description: Namespaces to be selected for Rules discovery. If unspecified,\n                  only the same namespace as the ThanosRuler object is in is used.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              ruleSelector:\n                description: A label selector to select which PrometheusRules to mount\n                  for alerting and recording.\n                properties:\n                  matchExpressions:\n                    description: matchExpressions is a list of label selector requirements.\n                      The requirements are ANDed.\n                    items:\n                      description: A label selector requirement is a selector that\n                        contains values, a key, and an operator that relates the key\n                        and values.\n                      properties:\n                        key:\n                          description: key is the label key that the selector applies\n                            to.\n                          type: string\n                        operator:\n                          description: operator represents a key's relationship to\n                            a set of values. Valid operators are In, NotIn, Exists\n                            and DoesNotExist.\n                          type: string\n                        values:\n                          description: values is an array of string values. If the\n                            operator is In or NotIn, the values array must be non-empty.\n                            If the operator is Exists or DoesNotExist, the values\n                            array must be empty. This array is replaced during a strategic\n                            merge patch.\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - key\n                      - operator\n                      type: object\n                    type: array\n                  matchLabels:\n                    additionalProperties:\n                      type: string\n                    description: matchLabels is a map of {key,value} pairs. A single\n                      {key,value} in the matchLabels map is equivalent to an element\n                      of matchExpressions, whose key field is \"key\", the operator\n                      is \"In\", and the values array contains only \"value\". The requirements\n                      are ANDed.\n                    type: object\n                type: object\n                x-kubernetes-map-type: atomic\n              securityContext:\n                description: SecurityContext holds pod-level security attributes and\n                  common container settings. This defaults to the default PodSecurityContext.\n                properties:\n                  fsGroup:\n                    description: \"A special supplemental group that applies to all\n                      containers in a pod. Some volume types allow the Kubelet to\n                      change the ownership of that volume to be owned by the pod:\n                      \\n 1. The owning GID will be the FSGroup 2. The setgid bit is\n                      set (new files created in the volume will be owned by FSGroup)\n                      3. The permission bits are OR'd with rw-rw---- \\n If unset,\n                      the Kubelet will not modify the ownership and permissions of\n                      any volume. Note that this field cannot be set when spec.os.name\n                      is windows.\"\n                    format: int64\n                    type: integer\n                  fsGroupChangePolicy:\n                    description: 'fsGroupChangePolicy defines behavior of changing\n                      ownership and permission of the volume before being exposed\n                      inside Pod. This field will only apply to volume types which\n                      support fsGroup based ownership(and permissions). It will have\n                      no effect on ephemeral volume types such as: secret, configmaps\n                      and emptydir. Valid values are \"OnRootMismatch\" and \"Always\".\n                      If not specified, \"Always\" is used. Note that this field cannot\n                      be set when spec.os.name is windows.'\n                    type: string\n                  runAsGroup:\n                    description: The GID to run the entrypoint of the container process.\n                      Uses runtime default if unset. May also be set in SecurityContext.  If\n                      set in both SecurityContext and PodSecurityContext, the value\n                      specified in SecurityContext takes precedence for that container.\n                      Note that this field cannot be set when spec.os.name is windows.\n                    format: int64\n                    type: integer\n                  runAsNonRoot:\n                    description: Indicates that the container must run as a non-root\n                      user. If true, the Kubelet will validate the image at runtime\n                      to ensure that it does not run as UID 0 (root) and fail to start\n                      the container if it does. If unset or false, no such validation\n                      will be performed. May also be set in SecurityContext.  If set\n                      in both SecurityContext and PodSecurityContext, the value specified\n                      in SecurityContext takes precedence.\n                    type: boolean\n                  runAsUser:\n                    description: The UID to run the entrypoint of the container process.\n                      Defaults to user specified in image metadata if unspecified.\n                      May also be set in SecurityContext.  If set in both SecurityContext\n                      and PodSecurityContext, the value specified in SecurityContext\n                      takes precedence for that container. Note that this field cannot\n                      be set when spec.os.name is windows.\n                    format: int64\n                    type: integer\n                  seLinuxOptions:\n                    description: The SELinux context to be applied to all containers.\n                      If unspecified, the container runtime will allocate a random\n                      SELinux context for each container.  May also be set in SecurityContext.  If\n                      set in both SecurityContext and PodSecurityContext, the value\n                      specified in SecurityContext takes precedence for that container.\n                      Note that this field cannot be set when spec.os.name is windows.\n                    properties:\n                      level:\n                        description: Level is SELinux level label that applies to\n                          the container.\n                        type: string\n                      role:\n                        description: Role is a SELinux role label that applies to\n                          the container.\n                        type: string\n                      type:\n                        description: Type is a SELinux type label that applies to\n                          the container.\n                        type: string\n                      user:\n                        description: User is a SELinux user label that applies to\n                          the container.\n                        type: string\n                    type: object\n                  seccompProfile:\n                    description: The seccomp options to use by the containers in this\n                      pod. Note that this field cannot be set when spec.os.name is\n                      windows.\n                    properties:\n                      localhostProfile:\n                        description: localhostProfile indicates a profile defined\n                          in a file on the node should be used. The profile must be\n                          preconfigured on the node to work. Must be a descending\n                          path, relative to the kubelet's configured seccomp profile\n                          location. Must be set if type is \"Localhost\". Must NOT be\n                          set for any other type.\n                        type: string\n                      type:\n                        description: \"type indicates which kind of seccomp profile\n                          will be applied. Valid options are: \\n Localhost - a profile\n                          defined in a file on the node should be used. RuntimeDefault\n                          - the container runtime default profile should be used.\n                          Unconfined - no profile should be applied.\"\n                        type: string\n                    required:\n                    - type\n                    type: object\n                  supplementalGroups:\n                    description: A list of groups applied to the first process run\n                      in each container, in addition to the container's primary GID,\n                      the fsGroup (if specified), and group memberships defined in\n                      the container image for the uid of the container process. If\n                      unspecified, no additional groups are added to any container.\n                      Note that group memberships defined in the container image for\n                      the uid of the container process are still effective, even if\n                      they are not included in this list. Note that this field cannot\n                      be set when spec.os.name is windows.\n                    items:\n                      format: int64\n                      type: integer\n                    type: array\n                  sysctls:\n                    description: Sysctls hold a list of namespaced sysctls used for\n                      the pod. Pods with unsupported sysctls (by the container runtime)\n                      might fail to launch. Note that this field cannot be set when\n                      spec.os.name is windows.\n                    items:\n                      description: Sysctl defines a kernel parameter to be set\n                      properties:\n                        name:\n                          description: Name of a property to set\n                          type: string\n                        value:\n                          description: Value of a property to set\n                          type: string\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  windowsOptions:\n                    description: The Windows specific settings applied to all containers.\n                      If unspecified, the options within a container's SecurityContext\n                      will be used. If set in both SecurityContext and PodSecurityContext,\n                      the value specified in SecurityContext takes precedence. Note\n                      that this field cannot be set when spec.os.name is linux.\n                    properties:\n                      gmsaCredentialSpec:\n                        description: GMSACredentialSpec is where the GMSA admission\n                          webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                          inlines the contents of the GMSA credential spec named by\n                          the GMSACredentialSpecName field.\n                        type: string\n                      gmsaCredentialSpecName:\n                        description: GMSACredentialSpecName is the name of the GMSA\n                          credential spec to use.\n                        type: string\n                      hostProcess:\n                        description: HostProcess determines if a container should\n                          be run as a 'Host Process' container. All of a Pod's containers\n                          must have the same effective HostProcess value (it is not\n                          allowed to have a mix of HostProcess containers and non-HostProcess\n                          containers). In addition, if HostProcess is true then HostNetwork\n                          must also be set to true.\n                        type: boolean\n                      runAsUserName:\n                        description: The UserName in Windows to run the entrypoint\n                          of the container process. Defaults to the user specified\n                          in image metadata if unspecified. May also be set in PodSecurityContext.\n                          If set in both SecurityContext and PodSecurityContext, the\n                          value specified in SecurityContext takes precedence.\n                        type: string\n                    type: object\n                type: object\n              serviceAccountName:\n                description: ServiceAccountName is the name of the ServiceAccount\n                  to use to run the Thanos Ruler Pods.\n                type: string\n              storage:\n                description: Storage spec to specify how storage shall be used.\n                properties:\n                  disableMountSubPath:\n                    description: 'Deprecated: subPath usage will be removed in a future\n                      release.'\n                    type: boolean\n                  emptyDir:\n                    description: 'EmptyDirVolumeSource to be used by the StatefulSet.\n                      If specified, it takes precedence over `ephemeral` and `volumeClaimTemplate`.\n                      More info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir'\n                    properties:\n                      medium:\n                        description: 'medium represents what type of storage medium\n                          should back this directory. The default is \"\" which means\n                          to use the node''s default medium. Must be an empty string\n                          (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                        type: string\n                      sizeLimit:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: 'sizeLimit is the total amount of local storage\n                          required for this EmptyDir volume. The size limit is also\n                          applicable for memory medium. The maximum usage on memory\n                          medium EmptyDir would be the minimum value between the SizeLimit\n                          specified here and the sum of memory limits of all containers\n                          in a pod. The default is nil which means that the limit\n                          is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                    type: object\n                  ephemeral:\n                    description: 'EphemeralVolumeSource to be used by the StatefulSet.\n                      This is a beta field in k8s 1.21 and GA in 1.15. For lower versions,\n                      starting with k8s 1.19, it requires enabling the GenericEphemeralVolume\n                      feature gate. More info: https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes'\n                    properties:\n                      volumeClaimTemplate:\n                        description: \"Will be used to create a stand-alone PVC to\n                          provision the volume. The pod in which this EphemeralVolumeSource\n                          is embedded will be the owner of the PVC, i.e. the PVC will\n                          be deleted together with the pod.  The name of the PVC will\n                          be `<pod name>-<volume name>` where `<volume name>` is the\n                          name from the `PodSpec.Volumes` array entry. Pod validation\n                          will reject the pod if the concatenated name is not valid\n                          for a PVC (for example, too long). \\n An existing PVC with\n                          that name that is not owned by the pod will *not* be used\n                          for the pod to avoid using an unrelated volume by mistake.\n                          Starting the pod is then blocked until the unrelated PVC\n                          is removed. If such a pre-created PVC is meant to be used\n                          by the pod, the PVC has to updated with an owner reference\n                          to the pod once the pod exists. Normally this should not\n                          be necessary, but it may be useful when manually reconstructing\n                          a broken cluster. \\n This field is read-only and no changes\n                          will be made by Kubernetes to the PVC after it has been\n                          created. \\n Required, must not be nil.\"\n                        properties:\n                          metadata:\n                            description: May contain labels and annotations that will\n                              be copied into the PVC when creating it. No other fields\n                              are allowed and will be rejected during validation.\n                            type: object\n                          spec:\n                            description: The specification for the PersistentVolumeClaim.\n                              The entire content is copied unchanged into the PVC\n                              that gets created from this template. The same fields\n                              as in a PersistentVolumeClaim are also valid here.\n                            properties:\n                              accessModes:\n                                description: 'accessModes contains the desired access\n                                  modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                                items:\n                                  type: string\n                                type: array\n                              dataSource:\n                                description: 'dataSource field can be used to specify\n                                  either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                                  * An existing PVC (PersistentVolumeClaim) If the\n                                  provisioner or an external controller can support\n                                  the specified data source, it will create a new\n                                  volume based on the contents of the specified data\n                                  source. When the AnyVolumeDataSource feature gate\n                                  is enabled, dataSource contents will be copied to\n                                  dataSourceRef, and dataSourceRef contents will be\n                                  copied to dataSource when dataSourceRef.namespace\n                                  is not specified. If the namespace is specified,\n                                  then dataSourceRef will not be copied to dataSource.'\n                                properties:\n                                  apiGroup:\n                                    description: APIGroup is the group for the resource\n                                      being referenced. If APIGroup is not specified,\n                                      the specified Kind must be in the core API group.\n                                      For any other third-party types, APIGroup is\n                                      required.\n                                    type: string\n                                  kind:\n                                    description: Kind is the type of resource being\n                                      referenced\n                                    type: string\n                                  name:\n                                    description: Name is the name of resource being\n                                      referenced\n                                    type: string\n                                required:\n                                - kind\n                                - name\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              dataSourceRef:\n                                description: 'dataSourceRef specifies the object from\n                                  which to populate the volume with data, if a non-empty\n                                  volume is desired. This may be any object from a\n                                  non-empty API group (non core object) or a PersistentVolumeClaim\n                                  object. When this field is specified, volume binding\n                                  will only succeed if the type of the specified object\n                                  matches some installed volume populator or dynamic\n                                  provisioner. This field will replace the functionality\n                                  of the dataSource field and as such if both fields\n                                  are non-empty, they must have the same value. For\n                                  backwards compatibility, when namespace isn''t specified\n                                  in dataSourceRef, both fields (dataSource and dataSourceRef)\n                                  will be set to the same value automatically if one\n                                  of them is empty and the other is non-empty. When\n                                  namespace is specified in dataSourceRef, dataSource\n                                  isn''t set to the same value and must be empty.\n                                  There are three important differences between dataSource\n                                  and dataSourceRef: * While dataSource only allows\n                                  two specific types of objects, dataSourceRef allows\n                                  any non-core object, as well as PersistentVolumeClaim\n                                  objects. * While dataSource ignores disallowed values\n                                  (dropping them), dataSourceRef preserves all values,\n                                  and generates an error if a disallowed value is\n                                  specified. * While dataSource only allows local\n                                  objects, dataSourceRef allows objects in any namespaces.\n                                  (Beta) Using this field requires the AnyVolumeDataSource\n                                  feature gate to be enabled. (Alpha) Using the namespace\n                                  field of dataSourceRef requires the CrossNamespaceVolumeDataSource\n                                  feature gate to be enabled.'\n                                properties:\n                                  apiGroup:\n                                    description: APIGroup is the group for the resource\n                                      being referenced. If APIGroup is not specified,\n                                      the specified Kind must be in the core API group.\n                                      For any other third-party types, APIGroup is\n                                      required.\n                                    type: string\n                                  kind:\n                                    description: Kind is the type of resource being\n                                      referenced\n                                    type: string\n                                  name:\n                                    description: Name is the name of resource being\n                                      referenced\n                                    type: string\n                                  namespace:\n                                    description: Namespace is the namespace of resource\n                                      being referenced Note that when a namespace\n                                      is specified, a gateway.networking.k8s.io/ReferenceGrant\n                                      object is required in the referent namespace\n                                      to allow that namespace's owner to accept the\n                                      reference. See the ReferenceGrant documentation\n                                      for details. (Alpha) This field requires the\n                                      CrossNamespaceVolumeDataSource feature gate\n                                      to be enabled.\n                                    type: string\n                                required:\n                                - kind\n                                - name\n                                type: object\n                              resources:\n                                description: 'resources represents the minimum resources\n                                  the volume should have. If RecoverVolumeExpansionFailure\n                                  feature is enabled users are allowed to specify\n                                  resource requirements that are lower than previous\n                                  value but must still be higher than capacity recorded\n                                  in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                                properties:\n                                  claims:\n                                    description: \"Claims lists the names of resources,\n                                      defined in spec.resourceClaims, that are used\n                                      by this container. \\n This is an alpha field\n                                      and requires enabling the DynamicResourceAllocation\n                                      feature gate. \\n This field is immutable. It\n                                      can only be set for containers.\"\n                                    items:\n                                      description: ResourceClaim references one entry\n                                        in PodSpec.ResourceClaims.\n                                      properties:\n                                        name:\n                                          description: Name must match the name of\n                                            one entry in pod.spec.resourceClaims of\n                                            the Pod where this field is used. It makes\n                                            that resource available inside a container.\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Limits describes the maximum amount\n                                      of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Requests describes the minimum amount\n                                      of compute resources required. If Requests is\n                                      omitted for a container, it defaults to Limits\n                                      if that is explicitly specified, otherwise to\n                                      an implementation-defined value. Requests cannot\n                                      exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                    type: object\n                                type: object\n                              selector:\n                                description: selector is a label query over volumes\n                                  to consider for binding.\n                                properties:\n                                  matchExpressions:\n                                    description: matchExpressions is a list of label\n                                      selector requirements. The requirements are\n                                      ANDed.\n                                    items:\n                                      description: A label selector requirement is\n                                        a selector that contains values, a key, and\n                                        an operator that relates the key and values.\n                                      properties:\n                                        key:\n                                          description: key is the label key that the\n                                            selector applies to.\n                                          type: string\n                                        operator:\n                                          description: operator represents a key's\n                                            relationship to a set of values. Valid\n                                            operators are In, NotIn, Exists and DoesNotExist.\n                                          type: string\n                                        values:\n                                          description: values is an array of string\n                                            values. If the operator is In or NotIn,\n                                            the values array must be non-empty. If\n                                            the operator is Exists or DoesNotExist,\n                                            the values array must be empty. This array\n                                            is replaced during a strategic merge patch.\n                                          items:\n                                            type: string\n                                          type: array\n                                      required:\n                                      - key\n                                      - operator\n                                      type: object\n                                    type: array\n                                  matchLabels:\n                                    additionalProperties:\n                                      type: string\n                                    description: matchLabels is a map of {key,value}\n                                      pairs. A single {key,value} in the matchLabels\n                                      map is equivalent to an element of matchExpressions,\n                                      whose key field is \"key\", the operator is \"In\",\n                                      and the values array contains only \"value\".\n                                      The requirements are ANDed.\n                                    type: object\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              storageClassName:\n                                description: 'storageClassName is the name of the\n                                  StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                                type: string\n                              volumeMode:\n                                description: volumeMode defines what type of volume\n                                  is required by the claim. Value of Filesystem is\n                                  implied when not included in claim spec.\n                                type: string\n                              volumeName:\n                                description: volumeName is the binding reference to\n                                  the PersistentVolume backing this claim.\n                                type: string\n                            type: object\n                        required:\n                        - spec\n                        type: object\n                    type: object\n                  volumeClaimTemplate:\n                    description: Defines the PVC spec to be used by the Prometheus\n                      StatefulSets. The easiest way to use a volume that cannot be\n                      automatically provisioned is to use a label selector alongside\n                      manually created PersistentVolumes.\n                    properties:\n                      apiVersion:\n                        description: 'APIVersion defines the versioned schema of this\n                          representation of an object. Servers should convert recognized\n                          schemas to the latest internal value, and may reject unrecognized\n                          values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n                        type: string\n                      kind:\n                        description: 'Kind is a string value representing the REST\n                          resource this object represents. Servers may infer this\n                          from the endpoint the client submits requests to. Cannot\n                          be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n                        type: string\n                      metadata:\n                        description: EmbeddedMetadata contains metadata relevant to\n                          an EmbeddedResource.\n                        properties:\n                          annotations:\n                            additionalProperties:\n                              type: string\n                            description: 'Annotations is an unstructured key value\n                              map stored with a resource that may be set by external\n                              tools to store and retrieve arbitrary metadata. They\n                              are not queryable and should be preserved when modifying\n                              objects. More info: http://kubernetes.io/docs/user-guide/annotations'\n                            type: object\n                          labels:\n                            additionalProperties:\n                              type: string\n                            description: 'Map of string keys and values that can be\n                              used to organize and categorize (scope and select) objects.\n                              May match selectors of replication controllers and services.\n                              More info: http://kubernetes.io/docs/user-guide/labels'\n                            type: object\n                          name:\n                            description: 'Name must be unique within a namespace.\n                              Is required when creating resources, although some resources\n                              may allow a client to request the generation of an appropriate\n                              name automatically. Name is primarily intended for creation\n                              idempotence and configuration definition. Cannot be\n                              updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'\n                            type: string\n                        type: object\n                      spec:\n                        description: 'Defines the desired characteristics of a volume\n                          requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                        properties:\n                          accessModes:\n                            description: 'accessModes contains the desired access\n                              modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                            items:\n                              type: string\n                            type: array\n                          dataSource:\n                            description: 'dataSource field can be used to specify\n                              either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                              * An existing PVC (PersistentVolumeClaim) If the provisioner\n                              or an external controller can support the specified\n                              data source, it will create a new volume based on the\n                              contents of the specified data source. When the AnyVolumeDataSource\n                              feature gate is enabled, dataSource contents will be\n                              copied to dataSourceRef, and dataSourceRef contents\n                              will be copied to dataSource when dataSourceRef.namespace\n                              is not specified. If the namespace is specified, then\n                              dataSourceRef will not be copied to dataSource.'\n                            properties:\n                              apiGroup:\n                                description: APIGroup is the group for the resource\n                                  being referenced. If APIGroup is not specified,\n                                  the specified Kind must be in the core API group.\n                                  For any other third-party types, APIGroup is required.\n                                type: string\n                              kind:\n                                description: Kind is the type of resource being referenced\n                                type: string\n                              name:\n                                description: Name is the name of resource being referenced\n                                type: string\n                            required:\n                            - kind\n                            - name\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          dataSourceRef:\n                            description: 'dataSourceRef specifies the object from\n                              which to populate the volume with data, if a non-empty\n                              volume is desired. This may be any object from a non-empty\n                              API group (non core object) or a PersistentVolumeClaim\n                              object. When this field is specified, volume binding\n                              will only succeed if the type of the specified object\n                              matches some installed volume populator or dynamic provisioner.\n                              This field will replace the functionality of the dataSource\n                              field and as such if both fields are non-empty, they\n                              must have the same value. For backwards compatibility,\n                              when namespace isn''t specified in dataSourceRef, both\n                              fields (dataSource and dataSourceRef) will be set to\n                              the same value automatically if one of them is empty\n                              and the other is non-empty. When namespace is specified\n                              in dataSourceRef, dataSource isn''t set to the same\n                              value and must be empty. There are three important differences\n                              between dataSource and dataSourceRef: * While dataSource\n                              only allows two specific types of objects, dataSourceRef\n                              allows any non-core object, as well as PersistentVolumeClaim\n                              objects. * While dataSource ignores disallowed values\n                              (dropping them), dataSourceRef preserves all values,\n                              and generates an error if a disallowed value is specified.\n                              * While dataSource only allows local objects, dataSourceRef\n                              allows objects in any namespaces. (Beta) Using this\n                              field requires the AnyVolumeDataSource feature gate\n                              to be enabled. (Alpha) Using the namespace field of\n                              dataSourceRef requires the CrossNamespaceVolumeDataSource\n                              feature gate to be enabled.'\n                            properties:\n                              apiGroup:\n                                description: APIGroup is the group for the resource\n                                  being referenced. If APIGroup is not specified,\n                                  the specified Kind must be in the core API group.\n                                  For any other third-party types, APIGroup is required.\n                                type: string\n                              kind:\n                                description: Kind is the type of resource being referenced\n                                type: string\n                              name:\n                                description: Name is the name of resource being referenced\n                                type: string\n                              namespace:\n                                description: Namespace is the namespace of resource\n                                  being referenced Note that when a namespace is specified,\n                                  a gateway.networking.k8s.io/ReferenceGrant object\n                                  is required in the referent namespace to allow that\n                                  namespace's owner to accept the reference. See the\n                                  ReferenceGrant documentation for details. (Alpha)\n                                  This field requires the CrossNamespaceVolumeDataSource\n                                  feature gate to be enabled.\n                                type: string\n                            required:\n                            - kind\n                            - name\n                            type: object\n                          resources:\n                            description: 'resources represents the minimum resources\n                              the volume should have. If RecoverVolumeExpansionFailure\n                              feature is enabled users are allowed to specify resource\n                              requirements that are lower than previous value but\n                              must still be higher than capacity recorded in the status\n                              field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                            properties:\n                              claims:\n                                description: \"Claims lists the names of resources,\n                                  defined in spec.resourceClaims, that are used by\n                                  this container. \\n This is an alpha field and requires\n                                  enabling the DynamicResourceAllocation feature gate.\n                                  \\n This field is immutable. It can only be set for\n                                  containers.\"\n                                items:\n                                  description: ResourceClaim references one entry\n                                    in PodSpec.ResourceClaims.\n                                  properties:\n                                    name:\n                                      description: Name must match the name of one\n                                        entry in pod.spec.resourceClaims of the Pod\n                                        where this field is used. It makes that resource\n                                        available inside a container.\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              limits:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                description: 'Limits describes the maximum amount\n                                  of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                type: object\n                              requests:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                description: 'Requests describes the minimum amount\n                                  of compute resources required. If Requests is omitted\n                                  for a container, it defaults to Limits if that is\n                                  explicitly specified, otherwise to an implementation-defined\n                                  value. Requests cannot exceed Limits. More info:\n                                  https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                type: object\n                            type: object\n                          selector:\n                            description: selector is a label query over volumes to\n                              consider for binding.\n                            properties:\n                              matchExpressions:\n                                description: matchExpressions is a list of label selector\n                                  requirements. The requirements are ANDed.\n                                items:\n                                  description: A label selector requirement is a selector\n                                    that contains values, a key, and an operator that\n                                    relates the key and values.\n                                  properties:\n                                    key:\n                                      description: key is the label key that the selector\n                                        applies to.\n                                      type: string\n                                    operator:\n                                      description: operator represents a key's relationship\n                                        to a set of values. Valid operators are In,\n                                        NotIn, Exists and DoesNotExist.\n                                      type: string\n                                    values:\n                                      description: values is an array of string values.\n                                        If the operator is In or NotIn, the values\n                                        array must be non-empty. If the operator is\n                                        Exists or DoesNotExist, the values array must\n                                        be empty. This array is replaced during a\n                                        strategic merge patch.\n                                      items:\n                                        type: string\n                                      type: array\n                                  required:\n                                  - key\n                                  - operator\n                                  type: object\n                                type: array\n                              matchLabels:\n                                additionalProperties:\n                                  type: string\n                                description: matchLabels is a map of {key,value} pairs.\n                                  A single {key,value} in the matchLabels map is equivalent\n                                  to an element of matchExpressions, whose key field\n                                  is \"key\", the operator is \"In\", and the values array\n                                  contains only \"value\". The requirements are ANDed.\n                                type: object\n                            type: object\n                            x-kubernetes-map-type: atomic\n                          storageClassName:\n                            description: 'storageClassName is the name of the StorageClass\n                              required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                            type: string\n                          volumeMode:\n                            description: volumeMode defines what type of volume is\n                              required by the claim. Value of Filesystem is implied\n                              when not included in claim spec.\n                            type: string\n                          volumeName:\n                            description: volumeName is the binding reference to the\n                              PersistentVolume backing this claim.\n                            type: string\n                        type: object\n                      status:\n                        description: 'Deprecated: this field is never set.'\n                        properties:\n                          accessModes:\n                            description: 'accessModes contains the actual access modes\n                              the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                            items:\n                              type: string\n                            type: array\n                          allocatedResourceStatuses:\n                            additionalProperties:\n                              description: When a controller receives persistentvolume\n                                claim update with ClaimResourceStatus for a resource\n                                that it does not recognizes, then it should ignore\n                                that update and let other controllers handle it.\n                              type: string\n                            description: \"allocatedResourceStatuses stores status\n                              of resource being resized for the given PVC. Key names\n                              follow standard Kubernetes label syntax. Valid values\n                              are either: * Un-prefixed keys: - storage - the capacity\n                              of the volume. * Custom resources must use implementation-defined\n                              prefixed names such as \\\"example.com/my-custom-resource\\\"\n                              Apart from above values - keys that are unprefixed or\n                              have kubernetes.io prefix are considered reserved and\n                              hence may not be used. \\n ClaimResourceStatus can be\n                              in any of following states: - ControllerResizeInProgress:\n                              State set when resize controller starts resizing the\n                              volume in control-plane. - ControllerResizeFailed: State\n                              set when resize has failed in resize controller with\n                              a terminal error. - NodeResizePending: State set when\n                              resize controller has finished resizing the volume but\n                              further resizing of volume is needed on the node. -\n                              NodeResizeInProgress: State set when kubelet starts\n                              resizing the volume. - NodeResizeFailed: State set when\n                              resizing has failed in kubelet with a terminal error.\n                              Transient errors don't set NodeResizeFailed. For example:\n                              if expanding a PVC for more capacity - this field can\n                              be one of the following states: - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"ControllerResizeInProgress\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"ControllerResizeFailed\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizePending\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizeInProgress\\\" - pvc.status.allocatedResourceStatus['storage']\n                              = \\\"NodeResizeFailed\\\" When this field is not set, it\n                              means that no resize operation is in progress for the\n                              given PVC. \\n A controller that receives PVC update\n                              with previously unknown resourceName or ClaimResourceStatus\n                              should ignore the update for the purpose it was designed.\n                              For example - a controller that only is responsible\n                              for resizing capacity of the volume, should ignore PVC\n                              updates that change other valid resources associated\n                              with PVC. \\n This is an alpha field and requires enabling\n                              RecoverVolumeExpansionFailure feature.\"\n                            type: object\n                            x-kubernetes-map-type: granular\n                          allocatedResources:\n                            additionalProperties:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                              x-kubernetes-int-or-string: true\n                            description: \"allocatedResources tracks the resources\n                              allocated to a PVC including its capacity. Key names\n                              follow standard Kubernetes label syntax. Valid values\n                              are either: * Un-prefixed keys: - storage - the capacity\n                              of the volume. * Custom resources must use implementation-defined\n                              prefixed names such as \\\"example.com/my-custom-resource\\\"\n                              Apart from above values - keys that are unprefixed or\n                              have kubernetes.io prefix are considered reserved and\n                              hence may not be used. \\n Capacity reported here may\n                              be larger than the actual capacity when a volume expansion\n                              operation is requested. For storage quota, the larger\n                              value from allocatedResources and PVC.spec.resources\n                              is used. If allocatedResources is not set, PVC.spec.resources\n                              alone is used for quota calculation. If a volume expansion\n                              capacity request is lowered, allocatedResources is only\n                              lowered if there are no expansion operations in progress\n                              and if the actual volume capacity is equal or lower\n                              than the requested capacity. \\n A controller that receives\n                              PVC update with previously unknown resourceName should\n                              ignore the update for the purpose it was designed. For\n                              example - a controller that only is responsible for\n                              resizing capacity of the volume, should ignore PVC updates\n                              that change other valid resources associated with PVC.\n                              \\n This is an alpha field and requires enabling RecoverVolumeExpansionFailure\n                              feature.\"\n                            type: object\n                          capacity:\n                            additionalProperties:\n                              anyOf:\n                              - type: integer\n                              - type: string\n                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                              x-kubernetes-int-or-string: true\n                            description: capacity represents the actual resources\n                              of the underlying volume.\n                            type: object\n                          conditions:\n                            description: conditions is the current Condition of persistent\n                              volume claim. If underlying persistent volume is being\n                              resized then the Condition will be set to 'ResizeStarted'.\n                            items:\n                              description: PersistentVolumeClaimCondition contains\n                                details about state of pvc\n                              properties:\n                                lastProbeTime:\n                                  description: lastProbeTime is the time we probed\n                                    the condition.\n                                  format: date-time\n                                  type: string\n                                lastTransitionTime:\n                                  description: lastTransitionTime is the time the\n                                    condition transitioned from one status to another.\n                                  format: date-time\n                                  type: string\n                                message:\n                                  description: message is the human-readable message\n                                    indicating details about last transition.\n                                  type: string\n                                reason:\n                                  description: reason is a unique, this should be\n                                    a short, machine understandable string that gives\n                                    the reason for condition's last transition. If\n                                    it reports \"ResizeStarted\" that means the underlying\n                                    persistent volume is being resized.\n                                  type: string\n                                status:\n                                  type: string\n                                type:\n                                  description: PersistentVolumeClaimConditionType\n                                    is a valid value of PersistentVolumeClaimCondition.Type\n                                  type: string\n                              required:\n                              - status\n                              - type\n                              type: object\n                            type: array\n                          phase:\n                            description: phase represents the current phase of PersistentVolumeClaim.\n                            type: string\n                        type: object\n                    type: object\n                type: object\n              tolerations:\n                description: If specified, the pod's tolerations.\n                items:\n                  description: The pod this Toleration is attached to tolerates any\n                    taint that matches the triple <key,value,effect> using the matching\n                    operator <operator>.\n                  properties:\n                    effect:\n                      description: Effect indicates the taint effect to match. Empty\n                        means match all taint effects. When specified, allowed values\n                        are NoSchedule, PreferNoSchedule and NoExecute.\n                      type: string\n                    key:\n                      description: Key is the taint key that the toleration applies\n                        to. Empty means match all taint keys. If the key is empty,\n                        operator must be Exists; this combination means to match all\n                        values and all keys.\n                      type: string\n                    operator:\n                      description: Operator represents a key's relationship to the\n                        value. Valid operators are Exists and Equal. Defaults to Equal.\n                        Exists is equivalent to wildcard for value, so that a pod\n                        can tolerate all taints of a particular category.\n                      type: string\n                    tolerationSeconds:\n                      description: TolerationSeconds represents the period of time\n                        the toleration (which must be of effect NoExecute, otherwise\n                        this field is ignored) tolerates the taint. By default, it\n                        is not set, which means tolerate the taint forever (do not\n                        evict). Zero and negative values will be treated as 0 (evict\n                        immediately) by the system.\n                      format: int64\n                      type: integer\n                    value:\n                      description: Value is the taint value the toleration matches\n                        to. If the operator is Exists, the value should be empty,\n                        otherwise just a regular string.\n                      type: string\n                  type: object\n                type: array\n              topologySpreadConstraints:\n                description: If specified, the pod's topology spread constraints.\n                items:\n                  description: TopologySpreadConstraint specifies how to spread matching\n                    pods among the given topology.\n                  properties:\n                    labelSelector:\n                      description: LabelSelector is used to find matching pods. Pods\n                        that match this label selector are counted to determine the\n                        number of pods in their corresponding topology domain.\n                      properties:\n                        matchExpressions:\n                          description: matchExpressions is a list of label selector\n                            requirements. The requirements are ANDed.\n                          items:\n                            description: A label selector requirement is a selector\n                              that contains values, a key, and an operator that relates\n                              the key and values.\n                            properties:\n                              key:\n                                description: key is the label key that the selector\n                                  applies to.\n                                type: string\n                              operator:\n                                description: operator represents a key's relationship\n                                  to a set of values. Valid operators are In, NotIn,\n                                  Exists and DoesNotExist.\n                                type: string\n                              values:\n                                description: values is an array of string values.\n                                  If the operator is In or NotIn, the values array\n                                  must be non-empty. If the operator is Exists or\n                                  DoesNotExist, the values array must be empty. This\n                                  array is replaced during a strategic merge patch.\n                                items:\n                                  type: string\n                                type: array\n                            required:\n                            - key\n                            - operator\n                            type: object\n                          type: array\n                        matchLabels:\n                          additionalProperties:\n                            type: string\n                          description: matchLabels is a map of {key,value} pairs.\n                            A single {key,value} in the matchLabels map is equivalent\n                            to an element of matchExpressions, whose key field is\n                            \"key\", the operator is \"In\", and the values array contains\n                            only \"value\". The requirements are ANDed.\n                          type: object\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    matchLabelKeys:\n                      description: \"MatchLabelKeys is a set of pod label keys to select\n                        the pods over which spreading will be calculated. The keys\n                        are used to lookup values from the incoming pod labels, those\n                        key-value labels are ANDed with labelSelector to select the\n                        group of existing pods over which spreading will be calculated\n                        for the incoming pod. The same key is forbidden to exist in\n                        both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot\n                        be set when LabelSelector isn't set. Keys that don't exist\n                        in the incoming pod labels will be ignored. A null or empty\n                        list means only match against labelSelector. \\n This is a\n                        beta field and requires the MatchLabelKeysInPodTopologySpread\n                        feature gate to be enabled (enabled by default).\"\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    maxSkew:\n                      description: 'MaxSkew describes the degree to which pods may\n                        be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`,\n                        it is the maximum permitted difference between the number\n                        of matching pods in the target topology and the global minimum.\n                        The global minimum is the minimum number of matching pods\n                        in an eligible domain or zero if the number of eligible domains\n                        is less than MinDomains. For example, in a 3-zone cluster,\n                        MaxSkew is set to 1, and pods with the same labelSelector\n                        spread as 2/2/1: In this case, the global minimum is 1. |\n                        zone1 | zone2 | zone3 | |  P P  |  P P  |   P   | - if MaxSkew\n                        is 1, incoming pod can only be scheduled to zone3 to become\n                        2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1)\n                        on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming\n                        pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`,\n                        it is used to give higher precedence to topologies that satisfy\n                        it. It''s a required field. Default value is 1 and 0 is not\n                        allowed.'\n                      format: int32\n                      type: integer\n                    minDomains:\n                      description: \"MinDomains indicates a minimum number of eligible\n                        domains. When the number of eligible domains with matching\n                        topology keys is less than minDomains, Pod Topology Spread\n                        treats \\\"global minimum\\\" as 0, and then the calculation of\n                        Skew is performed. And when the number of eligible domains\n                        with matching topology keys equals or greater than minDomains,\n                        this value has no effect on scheduling. As a result, when\n                        the number of eligible domains is less than minDomains, scheduler\n                        won't schedule more than maxSkew Pods to those domains. If\n                        value is nil, the constraint behaves as if MinDomains is equal\n                        to 1. Valid values are integers greater than 0. When value\n                        is not nil, WhenUnsatisfiable must be DoNotSchedule. \\n For\n                        example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains\n                        is set to 5 and pods with the same labelSelector spread as\n                        2/2/2: | zone1 | zone2 | zone3 | |  P P  |  P P  |  P P  |\n                        The number of domains is less than 5(MinDomains), so \\\"global\n                        minimum\\\" is treated as 0. In this situation, new pod with\n                        the same labelSelector cannot be scheduled, because computed\n                        skew will be 3(3 - 0) if new Pod is scheduled to any of the\n                        three zones, it will violate MaxSkew. \\n This is a beta field\n                        and requires the MinDomainsInPodTopologySpread feature gate\n                        to be enabled (enabled by default).\"\n                      format: int32\n                      type: integer\n                    nodeAffinityPolicy:\n                      description: \"NodeAffinityPolicy indicates how we will treat\n                        Pod's nodeAffinity/nodeSelector when calculating pod topology\n                        spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector\n                        are included in the calculations. - Ignore: nodeAffinity/nodeSelector\n                        are ignored. All nodes are included in the calculations. \\n\n                        If this value is nil, the behavior is equivalent to the Honor\n                        policy. This is a beta-level feature default enabled by the\n                        NodeInclusionPolicyInPodTopologySpread feature flag.\"\n                      type: string\n                    nodeTaintsPolicy:\n                      description: \"NodeTaintsPolicy indicates how we will treat node\n                        taints when calculating pod topology spread skew. Options\n                        are: - Honor: nodes without taints, along with tainted nodes\n                        for which the incoming pod has a toleration, are included.\n                        - Ignore: node taints are ignored. All nodes are included.\n                        \\n If this value is nil, the behavior is equivalent to the\n                        Ignore policy. This is a beta-level feature default enabled\n                        by the NodeInclusionPolicyInPodTopologySpread feature flag.\"\n                      type: string\n                    topologyKey:\n                      description: TopologyKey is the key of node labels. Nodes that\n                        have a label with this key and identical values are considered\n                        to be in the same topology. We consider each <key, value>\n                        as a \"bucket\", and try to put balanced number of pods into\n                        each bucket. We define a domain as a particular instance of\n                        a topology. Also, we define an eligible domain as a domain\n                        whose nodes meet the requirements of nodeAffinityPolicy and\n                        nodeTaintsPolicy. e.g. If TopologyKey is \"kubernetes.io/hostname\",\n                        each Node is a domain of that topology. And, if TopologyKey\n                        is \"topology.kubernetes.io/zone\", each zone is a domain of\n                        that topology. It's a required field.\n                      type: string\n                    whenUnsatisfiable:\n                      description: 'WhenUnsatisfiable indicates how to deal with a\n                        pod if it doesn''t satisfy the spread constraint. - DoNotSchedule\n                        (default) tells the scheduler not to schedule it. - ScheduleAnyway\n                        tells the scheduler to schedule the pod in any location, but\n                        giving higher precedence to topologies that would help reduce\n                        the skew. A constraint is considered \"Unsatisfiable\" for an\n                        incoming pod if and only if every possible node assignment\n                        for that pod would violate \"MaxSkew\" on some topology. For\n                        example, in a 3-zone cluster, MaxSkew is set to 1, and pods\n                        with the same labelSelector spread as 3/1/1: | zone1 | zone2\n                        | zone3 | | P P P |   P   |   P   | If WhenUnsatisfiable is\n                        set to DoNotSchedule, incoming pod can only be scheduled to\n                        zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on\n                        zone2(zone3) satisfies MaxSkew(1). In other words, the cluster\n                        can still be imbalanced, but scheduler won''t make it *more*\n                        imbalanced. It''s a required field.'\n                      type: string\n                  required:\n                  - maxSkew\n                  - topologyKey\n                  - whenUnsatisfiable\n                  type: object\n                type: array\n              tracingConfig:\n                description: TracingConfig configures tracing in Thanos. This is an\n                  experimental feature, it may change in any upcoming release in a\n                  breaking way.\n                properties:\n                  key:\n                    description: The key of the secret to select from.  Must be a\n                      valid secret key.\n                    type: string\n                  name:\n                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      TODO: Add other useful fields. apiVersion, kind, uid?'\n                    type: string\n                  optional:\n                    description: Specify whether the Secret or its key must be defined\n                    type: boolean\n                required:\n                - key\n                type: object\n                x-kubernetes-map-type: atomic\n              tracingConfigFile:\n                description: TracingConfig specifies the path of the tracing configuration\n                  file. When used alongside with TracingConfig, TracingConfigFile\n                  takes precedence.\n                type: string\n              version:\n                description: Version of Thanos to be deployed.\n                type: string\n              volumeMounts:\n                description: VolumeMounts allows configuration of additional VolumeMounts\n                  on the output StatefulSet definition. VolumeMounts specified will\n                  be appended to other VolumeMounts in the ruler container, that are\n                  generated as a result of StorageSpec objects.\n                items:\n                  description: VolumeMount describes a mounting of a Volume within\n                    a container.\n                  properties:\n                    mountPath:\n                      description: Path within the container at which the volume should\n                        be mounted.  Must not contain ':'.\n                      type: string\n                    mountPropagation:\n                      description: mountPropagation determines how mounts are propagated\n                        from the host to container and the other way around. When\n                        not set, MountPropagationNone is used. This field is beta\n                        in 1.10.\n                      type: string\n                    name:\n                      description: This must match the Name of a Volume.\n                      type: string\n                    readOnly:\n                      description: Mounted read-only if true, read-write otherwise\n                        (false or unspecified). Defaults to false.\n                      type: boolean\n                    subPath:\n                      description: Path within the volume from which the container's\n                        volume should be mounted. Defaults to \"\" (volume's root).\n                      type: string\n                    subPathExpr:\n                      description: Expanded path within the volume from which the\n                        container's volume should be mounted. Behaves similarly to\n                        SubPath but environment variable references $(VAR_NAME) are\n                        expanded using the container's environment. Defaults to \"\"\n                        (volume's root). SubPathExpr and SubPath are mutually exclusive.\n                      type: string\n                  required:\n                  - mountPath\n                  - name\n                  type: object\n                type: array\n              volumes:\n                description: Volumes allows configuration of additional volumes on\n                  the output StatefulSet definition. Volumes specified will be appended\n                  to other volumes that are generated as a result of StorageSpec objects.\n                items:\n                  description: Volume represents a named volume in a pod that may\n                    be accessed by any container in the pod.\n                  properties:\n                    awsElasticBlockStore:\n                      description: 'awsElasticBlockStore represents an AWS Disk resource\n                        that is attached to a kubelet''s host machine and then exposed\n                        to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        partition:\n                          description: 'partition is the partition in the volume that\n                            you want to mount. If omitted, the default is to mount\n                            by volume name. Examples: For volume /dev/sda1, you specify\n                            the partition as \"1\". Similarly, the volume partition\n                            for /dev/sda is \"0\" (or you can leave the property empty).'\n                          format: int32\n                          type: integer\n                        readOnly:\n                          description: 'readOnly value true will force the readOnly\n                            setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                          type: boolean\n                        volumeID:\n                          description: 'volumeID is unique ID of the persistent disk\n                            resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    azureDisk:\n                      description: azureDisk represents an Azure Data Disk mount on\n                        the host and bind mount to the pod.\n                      properties:\n                        cachingMode:\n                          description: 'cachingMode is the Host Caching mode: None,\n                            Read Only, Read Write.'\n                          type: string\n                        diskName:\n                          description: diskName is the Name of the data disk in the\n                            blob storage\n                          type: string\n                        diskURI:\n                          description: diskURI is the URI of data disk in the blob\n                            storage\n                          type: string\n                        fsType:\n                          description: fsType is Filesystem type to mount. Must be\n                            a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        kind:\n                          description: 'kind expected values are Shared: multiple\n                            blob disks per storage account  Dedicated: single blob\n                            disk per storage account  Managed: azure managed data\n                            disk (only in managed availability set). defaults to shared'\n                          type: string\n                        readOnly:\n                          description: readOnly Defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                      required:\n                      - diskName\n                      - diskURI\n                      type: object\n                    azureFile:\n                      description: azureFile represents an Azure File Service mount\n                        on the host and bind mount to the pod.\n                      properties:\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretName:\n                          description: secretName is the  name of secret that contains\n                            Azure Storage Account Name and Key\n                          type: string\n                        shareName:\n                          description: shareName is the azure share Name\n                          type: string\n                      required:\n                      - secretName\n                      - shareName\n                      type: object\n                    cephfs:\n                      description: cephFS represents a Ceph FS mount on the host that\n                        shares a pod's lifetime\n                      properties:\n                        monitors:\n                          description: 'monitors is Required: Monitors is a collection\n                            of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          items:\n                            type: string\n                          type: array\n                        path:\n                          description: 'path is Optional: Used as the mounted root,\n                            rather than the full Ceph tree, default is /'\n                          type: string\n                        readOnly:\n                          description: 'readOnly is Optional: Defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: boolean\n                        secretFile:\n                          description: 'secretFile is Optional: SecretFile is the\n                            path to key ring for User, default is /etc/ceph/user.secret\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: string\n                        secretRef:\n                          description: 'secretRef is Optional: SecretRef is reference\n                            to the authentication secret for User, default is empty.\n                            More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        user:\n                          description: 'user is optional: User is the rados user name,\n                            default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                          type: string\n                      required:\n                      - monitors\n                      type: object\n                    cinder:\n                      description: 'cinder represents a cinder volume attached and\n                        mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to\n                            be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: string\n                        readOnly:\n                          description: 'readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                            More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is optional: points to a secret\n                            object containing parameters used to connect to OpenStack.'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        volumeID:\n                          description: 'volumeID used to identify the volume in cinder.\n                            More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    configMap:\n                      description: configMap represents a configMap that should populate\n                        this volume\n                      properties:\n                        defaultMode:\n                          description: 'defaultMode is optional: mode bits used to\n                            set permissions on created files by default. Must be an\n                            octal value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: items if unspecified, each key-value pair in\n                            the Data field of the referenced ConfigMap will be projected\n                            into the volume as a file whose name is the key and content\n                            is the value. If specified, the listed keys will be projected\n                            into the specified paths, and unlisted keys will not be\n                            present. If a key is specified which is not present in\n                            the ConfigMap, the volume setup will error unless it is\n                            marked optional. Paths must be relative and may not contain\n                            the '..' path or start with '..'.\n                          items:\n                            description: Maps a string key to a path within a volume.\n                            properties:\n                              key:\n                                description: key is the key to project.\n                                type: string\n                              mode:\n                                description: 'mode is Optional: mode bits used to\n                                  set permissions on this file. Must be an octal value\n                                  between 0000 and 0777 or a decimal value between\n                                  0 and 511. YAML accepts both octal and decimal values,\n                                  JSON requires decimal values for mode bits. If not\n                                  specified, the volume defaultMode will be used.\n                                  This might be in conflict with other options that\n                                  affect the file mode, like fsGroup, and the result\n                                  can be other mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: path is the relative path of the file\n                                  to map the key to. May not be an absolute path.\n                                  May not contain the path element '..'. May not start\n                                  with the string '..'.\n                                type: string\n                            required:\n                            - key\n                            - path\n                            type: object\n                          type: array\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Add other useful fields. apiVersion, kind, uid?'\n                          type: string\n                        optional:\n                          description: optional specify whether the ConfigMap or its\n                            keys must be defined\n                          type: boolean\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    csi:\n                      description: csi (Container Storage Interface) represents ephemeral\n                        storage that is handled by certain external CSI drivers (Beta\n                        feature).\n                      properties:\n                        driver:\n                          description: driver is the name of the CSI driver that handles\n                            this volume. Consult with your admin for the correct name\n                            as registered in the cluster.\n                          type: string\n                        fsType:\n                          description: fsType to mount. Ex. \"ext4\", \"xfs\", \"ntfs\".\n                            If not provided, the empty value is passed to the associated\n                            CSI driver which will determine the default filesystem\n                            to apply.\n                          type: string\n                        nodePublishSecretRef:\n                          description: nodePublishSecretRef is a reference to the\n                            secret object containing sensitive information to pass\n                            to the CSI driver to complete the CSI NodePublishVolume\n                            and NodeUnpublishVolume calls. This field is optional,\n                            and  may be empty if no secret is required. If the secret\n                            object contains more than one secret, all secret references\n                            are passed.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        readOnly:\n                          description: readOnly specifies a read-only configuration\n                            for the volume. Defaults to false (read/write).\n                          type: boolean\n                        volumeAttributes:\n                          additionalProperties:\n                            type: string\n                          description: volumeAttributes stores driver-specific properties\n                            that are passed to the CSI driver. Consult your driver's\n                            documentation for supported values.\n                          type: object\n                      required:\n                      - driver\n                      type: object\n                    downwardAPI:\n                      description: downwardAPI represents downward API about the pod\n                        that should populate this volume\n                      properties:\n                        defaultMode:\n                          description: 'Optional: mode bits to use on created files\n                            by default. Must be a Optional: mode bits used to set\n                            permissions on created files by default. Must be an octal\n                            value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: Items is a list of downward API volume file\n                          items:\n                            description: DownwardAPIVolumeFile represents information\n                              to create the file containing the pod field\n                            properties:\n                              fieldRef:\n                                description: 'Required: Selects a field of the pod:\n                                  only annotations, labels, name and namespace are\n                                  supported.'\n                                properties:\n                                  apiVersion:\n                                    description: Version of the schema the FieldPath\n                                      is written in terms of, defaults to \"v1\".\n                                    type: string\n                                  fieldPath:\n                                    description: Path of the field to select in the\n                                      specified API version.\n                                    type: string\n                                required:\n                                - fieldPath\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              mode:\n                                description: 'Optional: mode bits used to set permissions\n                                  on this file, must be an octal value between 0000\n                                  and 0777 or a decimal value between 0 and 511. YAML\n                                  accepts both octal and decimal values, JSON requires\n                                  decimal values for mode bits. If not specified,\n                                  the volume defaultMode will be used. This might\n                                  be in conflict with other options that affect the\n                                  file mode, like fsGroup, and the result can be other\n                                  mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: 'Required: Path is  the relative path\n                                  name of the file to be created. Must not be absolute\n                                  or contain the ''..'' path. Must be utf-8 encoded.\n                                  The first item of the relative path must not start\n                                  with ''..'''\n                                type: string\n                              resourceFieldRef:\n                                description: 'Selects a resource of the container:\n                                  only resources limits and requests (limits.cpu,\n                                  limits.memory, requests.cpu and requests.memory)\n                                  are currently supported.'\n                                properties:\n                                  containerName:\n                                    description: 'Container name: required for volumes,\n                                      optional for env vars'\n                                    type: string\n                                  divisor:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: Specifies the output format of the\n                                      exposed resources, defaults to \"1\"\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  resource:\n                                    description: 'Required: resource to select'\n                                    type: string\n                                required:\n                                - resource\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            required:\n                            - path\n                            type: object\n                          type: array\n                      type: object\n                    emptyDir:\n                      description: 'emptyDir represents a temporary directory that\n                        shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                      properties:\n                        medium:\n                          description: 'medium represents what type of storage medium\n                            should back this directory. The default is \"\" which means\n                            to use the node''s default medium. Must be an empty string\n                            (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                          type: string\n                        sizeLimit:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          description: 'sizeLimit is the total amount of local storage\n                            required for this EmptyDir volume. The size limit is also\n                            applicable for memory medium. The maximum usage on memory\n                            medium EmptyDir would be the minimum value between the\n                            SizeLimit specified here and the sum of memory limits\n                            of all containers in a pod. The default is nil which means\n                            that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                      type: object\n                    ephemeral:\n                      description: \"ephemeral represents a volume that is handled\n                        by a cluster storage driver. The volume's lifecycle is tied\n                        to the pod that defines it - it will be created before the\n                        pod starts, and deleted when the pod is removed. \\n Use this\n                        if: a) the volume is only needed while the pod runs, b) features\n                        of normal volumes like restoring from snapshot or capacity\n                        tracking are needed, c) the storage driver is specified through\n                        a storage class, and d) the storage driver supports dynamic\n                        volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource\n                        for more information on the connection between this volume\n                        type and PersistentVolumeClaim). \\n Use PersistentVolumeClaim\n                        or one of the vendor-specific APIs for volumes that persist\n                        for longer than the lifecycle of an individual pod. \\n Use\n                        CSI for light-weight local ephemeral volumes if the CSI driver\n                        is meant to be used that way - see the documentation of the\n                        driver for more information. \\n A pod can use both types of\n                        ephemeral volumes and persistent volumes at the same time.\"\n                      properties:\n                        volumeClaimTemplate:\n                          description: \"Will be used to create a stand-alone PVC to\n                            provision the volume. The pod in which this EphemeralVolumeSource\n                            is embedded will be the owner of the PVC, i.e. the PVC\n                            will be deleted together with the pod.  The name of the\n                            PVC will be `<pod name>-<volume name>` where `<volume\n                            name>` is the name from the `PodSpec.Volumes` array entry.\n                            Pod validation will reject the pod if the concatenated\n                            name is not valid for a PVC (for example, too long). \\n\n                            An existing PVC with that name that is not owned by the\n                            pod will *not* be used for the pod to avoid using an unrelated\n                            volume by mistake. Starting the pod is then blocked until\n                            the unrelated PVC is removed. If such a pre-created PVC\n                            is meant to be used by the pod, the PVC has to updated\n                            with an owner reference to the pod once the pod exists.\n                            Normally this should not be necessary, but it may be useful\n                            when manually reconstructing a broken cluster. \\n This\n                            field is read-only and no changes will be made by Kubernetes\n                            to the PVC after it has been created. \\n Required, must\n                            not be nil.\"\n                          properties:\n                            metadata:\n                              description: May contain labels and annotations that\n                                will be copied into the PVC when creating it. No other\n                                fields are allowed and will be rejected during validation.\n                              type: object\n                            spec:\n                              description: The specification for the PersistentVolumeClaim.\n                                The entire content is copied unchanged into the PVC\n                                that gets created from this template. The same fields\n                                as in a PersistentVolumeClaim are also valid here.\n                              properties:\n                                accessModes:\n                                  description: 'accessModes contains the desired access\n                                    modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1'\n                                  items:\n                                    type: string\n                                  type: array\n                                dataSource:\n                                  description: 'dataSource field can be used to specify\n                                    either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n                                    * An existing PVC (PersistentVolumeClaim) If the\n                                    provisioner or an external controller can support\n                                    the specified data source, it will create a new\n                                    volume based on the contents of the specified\n                                    data source. When the AnyVolumeDataSource feature\n                                    gate is enabled, dataSource contents will be copied\n                                    to dataSourceRef, and dataSourceRef contents will\n                                    be copied to dataSource when dataSourceRef.namespace\n                                    is not specified. If the namespace is specified,\n                                    then dataSourceRef will not be copied to dataSource.'\n                                  properties:\n                                    apiGroup:\n                                      description: APIGroup is the group for the resource\n                                        being referenced. If APIGroup is not specified,\n                                        the specified Kind must be in the core API\n                                        group. For any other third-party types, APIGroup\n                                        is required.\n                                      type: string\n                                    kind:\n                                      description: Kind is the type of resource being\n                                        referenced\n                                      type: string\n                                    name:\n                                      description: Name is the name of resource being\n                                        referenced\n                                      type: string\n                                  required:\n                                  - kind\n                                  - name\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                dataSourceRef:\n                                  description: 'dataSourceRef specifies the object\n                                    from which to populate the volume with data, if\n                                    a non-empty volume is desired. This may be any\n                                    object from a non-empty API group (non core object)\n                                    or a PersistentVolumeClaim object. When this field\n                                    is specified, volume binding will only succeed\n                                    if the type of the specified object matches some\n                                    installed volume populator or dynamic provisioner.\n                                    This field will replace the functionality of the\n                                    dataSource field and as such if both fields are\n                                    non-empty, they must have the same value. For\n                                    backwards compatibility, when namespace isn''t\n                                    specified in dataSourceRef, both fields (dataSource\n                                    and dataSourceRef) will be set to the same value\n                                    automatically if one of them is empty and the\n                                    other is non-empty. When namespace is specified\n                                    in dataSourceRef, dataSource isn''t set to the\n                                    same value and must be empty. There are three\n                                    important differences between dataSource and dataSourceRef:\n                                    * While dataSource only allows two specific types\n                                    of objects, dataSourceRef allows any non-core\n                                    object, as well as PersistentVolumeClaim objects.\n                                    * While dataSource ignores disallowed values (dropping\n                                    them), dataSourceRef preserves all values, and\n                                    generates an error if a disallowed value is specified.\n                                    * While dataSource only allows local objects,\n                                    dataSourceRef allows objects in any namespaces.\n                                    (Beta) Using this field requires the AnyVolumeDataSource\n                                    feature gate to be enabled. (Alpha) Using the\n                                    namespace field of dataSourceRef requires the\n                                    CrossNamespaceVolumeDataSource feature gate to\n                                    be enabled.'\n                                  properties:\n                                    apiGroup:\n                                      description: APIGroup is the group for the resource\n                                        being referenced. If APIGroup is not specified,\n                                        the specified Kind must be in the core API\n                                        group. For any other third-party types, APIGroup\n                                        is required.\n                                      type: string\n                                    kind:\n                                      description: Kind is the type of resource being\n                                        referenced\n                                      type: string\n                                    name:\n                                      description: Name is the name of resource being\n                                        referenced\n                                      type: string\n                                    namespace:\n                                      description: Namespace is the namespace of resource\n                                        being referenced Note that when a namespace\n                                        is specified, a gateway.networking.k8s.io/ReferenceGrant\n                                        object is required in the referent namespace\n                                        to allow that namespace's owner to accept\n                                        the reference. See the ReferenceGrant documentation\n                                        for details. (Alpha) This field requires the\n                                        CrossNamespaceVolumeDataSource feature gate\n                                        to be enabled.\n                                      type: string\n                                  required:\n                                  - kind\n                                  - name\n                                  type: object\n                                resources:\n                                  description: 'resources represents the minimum resources\n                                    the volume should have. If RecoverVolumeExpansionFailure\n                                    feature is enabled users are allowed to specify\n                                    resource requirements that are lower than previous\n                                    value but must still be higher than capacity recorded\n                                    in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'\n                                  properties:\n                                    claims:\n                                      description: \"Claims lists the names of resources,\n                                        defined in spec.resourceClaims, that are used\n                                        by this container. \\n This is an alpha field\n                                        and requires enabling the DynamicResourceAllocation\n                                        feature gate. \\n This field is immutable.\n                                        It can only be set for containers.\"\n                                      items:\n                                        description: ResourceClaim references one\n                                          entry in PodSpec.ResourceClaims.\n                                        properties:\n                                          name:\n                                            description: Name must match the name\n                                              of one entry in pod.spec.resourceClaims\n                                              of the Pod where this field is used.\n                                              It makes that resource available inside\n                                              a container.\n                                            type: string\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    limits:\n                                      additionalProperties:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                        x-kubernetes-int-or-string: true\n                                      description: 'Limits describes the maximum amount\n                                        of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                      type: object\n                                    requests:\n                                      additionalProperties:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                        x-kubernetes-int-or-string: true\n                                      description: 'Requests describes the minimum\n                                        amount of compute resources required. If Requests\n                                        is omitted for a container, it defaults to\n                                        Limits if that is explicitly specified, otherwise\n                                        to an implementation-defined value. Requests\n                                        cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'\n                                      type: object\n                                  type: object\n                                selector:\n                                  description: selector is a label query over volumes\n                                    to consider for binding.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: A label selector requirement\n                                          is a selector that contains values, a key,\n                                          and an operator that relates the key and\n                                          values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: operator represents a key's\n                                              relationship to a set of values. Valid\n                                              operators are In, NotIn, Exists and\n                                              DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: values is an array of string\n                                              values. If the operator is In or NotIn,\n                                              the values array must be non-empty.\n                                              If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This\n                                              array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: matchLabels is a map of {key,value}\n                                        pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions,\n                                        whose key field is \"key\", the operator is\n                                        \"In\", and the values array contains only \"value\".\n                                        The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                storageClassName:\n                                  description: 'storageClassName is the name of the\n                                    StorageClass required by the claim. More info:\n                                    https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1'\n                                  type: string\n                                volumeMode:\n                                  description: volumeMode defines what type of volume\n                                    is required by the claim. Value of Filesystem\n                                    is implied when not included in claim spec.\n                                  type: string\n                                volumeName:\n                                  description: volumeName is the binding reference\n                                    to the PersistentVolume backing this claim.\n                                  type: string\n                              type: object\n                          required:\n                          - spec\n                          type: object\n                      type: object\n                    fc:\n                      description: fc represents a Fibre Channel resource that is\n                        attached to a kubelet's host machine and then exposed to the\n                        pod.\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. TODO: how do we prevent errors in the\n                            filesystem from compromising the machine'\n                          type: string\n                        lun:\n                          description: 'lun is Optional: FC target lun number'\n                          format: int32\n                          type: integer\n                        readOnly:\n                          description: 'readOnly is Optional: Defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.'\n                          type: boolean\n                        targetWWNs:\n                          description: 'targetWWNs is Optional: FC target worldwide\n                            names (WWNs)'\n                          items:\n                            type: string\n                          type: array\n                        wwids:\n                          description: 'wwids Optional: FC volume world wide identifiers\n                            (wwids) Either wwids or combination of targetWWNs and\n                            lun must be set, but not both simultaneously.'\n                          items:\n                            type: string\n                          type: array\n                      type: object\n                    flexVolume:\n                      description: flexVolume represents a generic volume resource\n                        that is provisioned/attached using an exec based plugin.\n                      properties:\n                        driver:\n                          description: driver is the name of the driver to use for\n                            this volume.\n                          type: string\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends\n                            on FlexVolume script.\n                          type: string\n                        options:\n                          additionalProperties:\n                            type: string\n                          description: 'options is Optional: this field holds extra\n                            command options if any.'\n                          type: object\n                        readOnly:\n                          description: 'readOnly is Optional: defaults to false (read/write).\n                            ReadOnly here will force the ReadOnly setting in VolumeMounts.'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is Optional: secretRef is reference\n                            to the secret object containing sensitive information\n                            to pass to the plugin scripts. This may be empty if no\n                            secret object is specified. If the secret object contains\n                            more than one secret, all secrets are passed to the plugin\n                            scripts.'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                      required:\n                      - driver\n                      type: object\n                    flocker:\n                      description: flocker represents a Flocker volume attached to\n                        a kubelet's host machine. This depends on the Flocker control\n                        service being running\n                      properties:\n                        datasetName:\n                          description: datasetName is Name of the dataset stored as\n                            metadata -> name on the dataset for Flocker should be\n                            considered as deprecated\n                          type: string\n                        datasetUUID:\n                          description: datasetUUID is the UUID of the dataset. This\n                            is unique identifier of a Flocker dataset\n                          type: string\n                      type: object\n                    gcePersistentDisk:\n                      description: 'gcePersistentDisk represents a GCE Disk resource\n                        that is attached to a kubelet''s host machine and then exposed\n                        to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                      properties:\n                        fsType:\n                          description: 'fsType is filesystem type of the volume that\n                            you want to mount. Tip: Ensure that the filesystem type\n                            is supported by the host operating system. Examples: \"ext4\",\n                            \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        partition:\n                          description: 'partition is the partition in the volume that\n                            you want to mount. If omitted, the default is to mount\n                            by volume name. Examples: For volume /dev/sda1, you specify\n                            the partition as \"1\". Similarly, the volume partition\n                            for /dev/sda is \"0\" (or you can leave the property empty).\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          format: int32\n                          type: integer\n                        pdName:\n                          description: 'pdName is unique name of the PD resource in\n                            GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                          type: boolean\n                      required:\n                      - pdName\n                      type: object\n                    gitRepo:\n                      description: 'gitRepo represents a git repository at a particular\n                        revision. DEPRECATED: GitRepo is deprecated. To provision\n                        a container with a git repo, mount an EmptyDir into an InitContainer\n                        that clones the repo using git, then mount the EmptyDir into\n                        the Pod''s container.'\n                      properties:\n                        directory:\n                          description: directory is the target directory name. Must\n                            not contain or start with '..'.  If '.' is supplied, the\n                            volume directory will be the git repository.  Otherwise,\n                            if specified, the volume will contain the git repository\n                            in the subdirectory with the given name.\n                          type: string\n                        repository:\n                          description: repository is the URL\n                          type: string\n                        revision:\n                          description: revision is the commit hash for the specified\n                            revision.\n                          type: string\n                      required:\n                      - repository\n                      type: object\n                    glusterfs:\n                      description: 'glusterfs represents a Glusterfs mount on the\n                        host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md'\n                      properties:\n                        endpoints:\n                          description: 'endpoints is the endpoint name that details\n                            Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: string\n                        path:\n                          description: 'path is the Glusterfs volume path. More info:\n                            https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the Glusterfs volume\n                            to be mounted with read-only permissions. Defaults to\n                            false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                          type: boolean\n                      required:\n                      - endpoints\n                      - path\n                      type: object\n                    hostPath:\n                      description: 'hostPath represents a pre-existing file or directory\n                        on the host machine that is directly exposed to the container.\n                        This is generally used for system agents or other privileged\n                        things that are allowed to see the host machine. Most containers\n                        will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n                        --- TODO(jonesdl) We need to restrict who can use host directory\n                        mounts and who can/can not mount host directories as read/write.'\n                      properties:\n                        path:\n                          description: 'path of the directory on the host. If the\n                            path is a symlink, it will follow the link to the real\n                            path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                          type: string\n                        type:\n                          description: 'type for HostPath Volume Defaults to \"\" More\n                            info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                          type: string\n                      required:\n                      - path\n                      type: object\n                    iscsi:\n                      description: 'iscsi represents an ISCSI Disk resource that is\n                        attached to a kubelet''s host machine and then exposed to\n                        the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md'\n                      properties:\n                        chapAuthDiscovery:\n                          description: chapAuthDiscovery defines whether support iSCSI\n                            Discovery CHAP authentication\n                          type: boolean\n                        chapAuthSession:\n                          description: chapAuthSession defines whether support iSCSI\n                            Session CHAP authentication\n                          type: boolean\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        initiatorName:\n                          description: initiatorName is the custom iSCSI Initiator\n                            Name. If initiatorName is specified with iscsiInterface\n                            simultaneously, new iSCSI interface <target portal>:<volume\n                            name> will be created for the connection.\n                          type: string\n                        iqn:\n                          description: iqn is the target iSCSI Qualified Name.\n                          type: string\n                        iscsiInterface:\n                          description: iscsiInterface is the interface Name that uses\n                            an iSCSI transport. Defaults to 'default' (tcp).\n                          type: string\n                        lun:\n                          description: lun represents iSCSI Target Lun number.\n                          format: int32\n                          type: integer\n                        portals:\n                          description: portals is the iSCSI Target Portal List. The\n                            portal is either an IP or ip_addr:port if the port is\n                            other than default (typically TCP ports 860 and 3260).\n                          items:\n                            type: string\n                          type: array\n                        readOnly:\n                          description: readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false.\n                          type: boolean\n                        secretRef:\n                          description: secretRef is the CHAP Secret for iSCSI target\n                            and initiator authentication\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        targetPortal:\n                          description: targetPortal is iSCSI Target Portal. The Portal\n                            is either an IP or ip_addr:port if the port is other than\n                            default (typically TCP ports 860 and 3260).\n                          type: string\n                      required:\n                      - iqn\n                      - lun\n                      - targetPortal\n                      type: object\n                    name:\n                      description: 'name of the volume. Must be a DNS_LABEL and unique\n                        within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'\n                      type: string\n                    nfs:\n                      description: 'nfs represents an NFS mount on the host that shares\n                        a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                      properties:\n                        path:\n                          description: 'path that is exported by the NFS server. More\n                            info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the NFS export to\n                            be mounted with read-only permissions. Defaults to false.\n                            More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: boolean\n                        server:\n                          description: 'server is the hostname or IP address of the\n                            NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                          type: string\n                      required:\n                      - path\n                      - server\n                      type: object\n                    persistentVolumeClaim:\n                      description: 'persistentVolumeClaimVolumeSource represents a\n                        reference to a PersistentVolumeClaim in the same namespace.\n                        More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                      properties:\n                        claimName:\n                          description: 'claimName is the name of a PersistentVolumeClaim\n                            in the same namespace as the pod using this volume. More\n                            info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                          type: string\n                        readOnly:\n                          description: readOnly Will force the ReadOnly setting in\n                            VolumeMounts. Default false.\n                          type: boolean\n                      required:\n                      - claimName\n                      type: object\n                    photonPersistentDisk:\n                      description: photonPersistentDisk represents a PhotonController\n                        persistent disk attached and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        pdID:\n                          description: pdID is the ID that identifies Photon Controller\n                            persistent disk\n                          type: string\n                      required:\n                      - pdID\n                      type: object\n                    portworxVolume:\n                      description: portworxVolume represents a portworx volume attached\n                        and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fSType represents the filesystem type to mount\n                            Must be a filesystem type supported by the host operating\n                            system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        volumeID:\n                          description: volumeID uniquely identifies a Portworx volume\n                          type: string\n                      required:\n                      - volumeID\n                      type: object\n                    projected:\n                      description: projected items for all in one resources secrets,\n                        configmaps, and downward API\n                      properties:\n                        defaultMode:\n                          description: defaultMode are the mode bits used to set permissions\n                            on created files by default. Must be an octal value between\n                            0000 and 0777 or a decimal value between 0 and 511. YAML\n                            accepts both octal and decimal values, JSON requires decimal\n                            values for mode bits. Directories within the path are\n                            not affected by this setting. This might be in conflict\n                            with other options that affect the file mode, like fsGroup,\n                            and the result can be other mode bits set.\n                          format: int32\n                          type: integer\n                        sources:\n                          description: sources is the list of volume projections\n                          items:\n                            description: Projection that may be projected along with\n                              other supported volume types\n                            properties:\n                              configMap:\n                                description: configMap information about the configMap\n                                  data to project\n                                properties:\n                                  items:\n                                    description: items if unspecified, each key-value\n                                      pair in the Data field of the referenced ConfigMap\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the ConfigMap, the volume\n                                      setup will error unless it is marked optional.\n                                      Paths must be relative and may not contain the\n                                      '..' path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: key is the key to project.\n                                          type: string\n                                        mode:\n                                          description: 'mode is Optional: mode bits\n                                            used to set permissions on this file.\n                                            Must be an octal value between 0000 and\n                                            0777 or a decimal value between 0 and\n                                            511. YAML accepts both octal and decimal\n                                            values, JSON requires decimal values for\n                                            mode bits. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: path is the relative path of\n                                            the file to map the key to. May not be\n                                            an absolute path. May not contain the\n                                            path element '..'. May not start with\n                                            the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: optional specify whether the ConfigMap\n                                      or its keys must be defined\n                                    type: boolean\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              downwardAPI:\n                                description: downwardAPI information about the downwardAPI\n                                  data to project\n                                properties:\n                                  items:\n                                    description: Items is a list of DownwardAPIVolume\n                                      file\n                                    items:\n                                      description: DownwardAPIVolumeFile represents\n                                        information to create the file containing\n                                        the pod field\n                                      properties:\n                                        fieldRef:\n                                          description: 'Required: Selects a field\n                                            of the pod: only annotations, labels,\n                                            name and namespace are supported.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        mode:\n                                          description: 'Optional: mode bits used to\n                                            set permissions on this file, must be\n                                            an octal value between 0000 and 0777 or\n                                            a decimal value between 0 and 511. YAML\n                                            accepts both octal and decimal values,\n                                            JSON requires decimal values for mode\n                                            bits. If not specified, the volume defaultMode\n                                            will be used. This might be in conflict\n                                            with other options that affect the file\n                                            mode, like fsGroup, and the result can\n                                            be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: 'Required: Path is  the relative\n                                            path name of the file to be created. Must\n                                            not be absolute or contain the ''..''\n                                            path. Must be utf-8 encoded. The first\n                                            item of the relative path must not start\n                                            with ''..'''\n                                          type: string\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, requests.cpu\n                                            and requests.memory) are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - path\n                                      type: object\n                                    type: array\n                                type: object\n                              secret:\n                                description: secret information about the secret data\n                                  to project\n                                properties:\n                                  items:\n                                    description: items if unspecified, each key-value\n                                      pair in the Data field of the referenced Secret\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the Secret, the volume setup\n                                      will error unless it is marked optional. Paths\n                                      must be relative and may not contain the '..'\n                                      path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: key is the key to project.\n                                          type: string\n                                        mode:\n                                          description: 'mode is Optional: mode bits\n                                            used to set permissions on this file.\n                                            Must be an octal value between 0000 and\n                                            0777 or a decimal value between 0 and\n                                            511. YAML accepts both octal and decimal\n                                            values, JSON requires decimal values for\n                                            mode bits. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: path is the relative path of\n                                            the file to map the key to. May not be\n                                            an absolute path. May not contain the\n                                            path element '..'. May not start with\n                                            the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: optional field specify whether the\n                                      Secret or its key must be defined\n                                    type: boolean\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              serviceAccountToken:\n                                description: serviceAccountToken is information about\n                                  the serviceAccountToken data to project\n                                properties:\n                                  audience:\n                                    description: audience is the intended audience\n                                      of the token. A recipient of a token must identify\n                                      itself with an identifier specified in the audience\n                                      of the token, and otherwise should reject the\n                                      token. The audience defaults to the identifier\n                                      of the apiserver.\n                                    type: string\n                                  expirationSeconds:\n                                    description: expirationSeconds is the requested\n                                      duration of validity of the service account\n                                      token. As the token approaches expiration, the\n                                      kubelet volume plugin will proactively rotate\n                                      the service account token. The kubelet will\n                                      start trying to rotate the token if the token\n                                      is older than 80 percent of its time to live\n                                      or if the token is older than 24 hours.Defaults\n                                      to 1 hour and must be at least 10 minutes.\n                                    format: int64\n                                    type: integer\n                                  path:\n                                    description: path is the path relative to the\n                                      mount point of the file to project the token\n                                      into.\n                                    type: string\n                                required:\n                                - path\n                                type: object\n                            type: object\n                          type: array\n                      type: object\n                    quobyte:\n                      description: quobyte represents a Quobyte mount on the host\n                        that shares a pod's lifetime\n                      properties:\n                        group:\n                          description: group to map volume access to Default is no\n                            group\n                          type: string\n                        readOnly:\n                          description: readOnly here will force the Quobyte volume\n                            to be mounted with read-only permissions. Defaults to\n                            false.\n                          type: boolean\n                        registry:\n                          description: registry represents a single or multiple Quobyte\n                            Registry services specified as a string as host:port pair\n                            (multiple entries are separated with commas) which acts\n                            as the central registry for volumes\n                          type: string\n                        tenant:\n                          description: tenant owning the given Quobyte volume in the\n                            Backend Used with dynamically provisioned Quobyte volumes,\n                            value is set by the plugin\n                          type: string\n                        user:\n                          description: user to map volume access to Defaults to serivceaccount\n                            user\n                          type: string\n                        volume:\n                          description: volume is a string that references an already\n                            created Quobyte volume by name.\n                          type: string\n                      required:\n                      - registry\n                      - volume\n                      type: object\n                    rbd:\n                      description: 'rbd represents a Rados Block Device mount on the\n                        host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md'\n                      properties:\n                        fsType:\n                          description: 'fsType is the filesystem type of the volume\n                            that you want to mount. Tip: Ensure that the filesystem\n                            type is supported by the host operating system. Examples:\n                            \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd\n                            TODO: how do we prevent errors in the filesystem from\n                            compromising the machine'\n                          type: string\n                        image:\n                          description: 'image is the rados image name. More info:\n                            https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        keyring:\n                          description: 'keyring is the path to key ring for RBDUser.\n                            Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        monitors:\n                          description: 'monitors is a collection of Ceph monitors.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          items:\n                            type: string\n                          type: array\n                        pool:\n                          description: 'pool is the rados pool name. Default is rbd.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                        readOnly:\n                          description: 'readOnly here will force the ReadOnly setting\n                            in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: boolean\n                        secretRef:\n                          description: 'secretRef is name of the authentication secret\n                            for RBDUser. If provided overrides keyring. Default is\n                            nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        user:\n                          description: 'user is the rados user name. Default is admin.\n                            More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                          type: string\n                      required:\n                      - image\n                      - monitors\n                      type: object\n                    scaleIO:\n                      description: scaleIO represents a ScaleIO persistent volume\n                        attached and mounted on Kubernetes nodes.\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\".\n                          type: string\n                        gateway:\n                          description: gateway is the host address of the ScaleIO\n                            API Gateway.\n                          type: string\n                        protectionDomain:\n                          description: protectionDomain is the name of the ScaleIO\n                            Protection Domain for the configured storage.\n                          type: string\n                        readOnly:\n                          description: readOnly Defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretRef:\n                          description: secretRef references to the secret for ScaleIO\n                            user and other sensitive information. If this is not provided,\n                            Login operation will fail.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        sslEnabled:\n                          description: sslEnabled Flag enable/disable SSL communication\n                            with Gateway, default false\n                          type: boolean\n                        storageMode:\n                          description: storageMode indicates whether the storage for\n                            a volume should be ThickProvisioned or ThinProvisioned.\n                            Default is ThinProvisioned.\n                          type: string\n                        storagePool:\n                          description: storagePool is the ScaleIO Storage Pool associated\n                            with the protection domain.\n                          type: string\n                        system:\n                          description: system is the name of the storage system as\n                            configured in ScaleIO.\n                          type: string\n                        volumeName:\n                          description: volumeName is the name of a volume already\n                            created in the ScaleIO system that is associated with\n                            this volume source.\n                          type: string\n                      required:\n                      - gateway\n                      - secretRef\n                      - system\n                      type: object\n                    secret:\n                      description: 'secret represents a secret that should populate\n                        this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                      properties:\n                        defaultMode:\n                          description: 'defaultMode is Optional: mode bits used to\n                            set permissions on created files by default. Must be an\n                            octal value between 0000 and 0777 or a decimal value between\n                            0 and 511. YAML accepts both octal and decimal values,\n                            JSON requires decimal values for mode bits. Defaults to\n                            0644. Directories within the path are not affected by\n                            this setting. This might be in conflict with other options\n                            that affect the file mode, like fsGroup, and the result\n                            can be other mode bits set.'\n                          format: int32\n                          type: integer\n                        items:\n                          description: items If unspecified, each key-value pair in\n                            the Data field of the referenced Secret will be projected\n                            into the volume as a file whose name is the key and content\n                            is the value. If specified, the listed keys will be projected\n                            into the specified paths, and unlisted keys will not be\n                            present. If a key is specified which is not present in\n                            the Secret, the volume setup will error unless it is marked\n                            optional. Paths must be relative and may not contain the\n                            '..' path or start with '..'.\n                          items:\n                            description: Maps a string key to a path within a volume.\n                            properties:\n                              key:\n                                description: key is the key to project.\n                                type: string\n                              mode:\n                                description: 'mode is Optional: mode bits used to\n                                  set permissions on this file. Must be an octal value\n                                  between 0000 and 0777 or a decimal value between\n                                  0 and 511. YAML accepts both octal and decimal values,\n                                  JSON requires decimal values for mode bits. If not\n                                  specified, the volume defaultMode will be used.\n                                  This might be in conflict with other options that\n                                  affect the file mode, like fsGroup, and the result\n                                  can be other mode bits set.'\n                                format: int32\n                                type: integer\n                              path:\n                                description: path is the relative path of the file\n                                  to map the key to. May not be an absolute path.\n                                  May not contain the path element '..'. May not start\n                                  with the string '..'.\n                                type: string\n                            required:\n                            - key\n                            - path\n                            type: object\n                          type: array\n                        optional:\n                          description: optional field specify whether the Secret or\n                            its keys must be defined\n                          type: boolean\n                        secretName:\n                          description: 'secretName is the name of the secret in the\n                            pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                          type: string\n                      type: object\n                    storageos:\n                      description: storageOS represents a StorageOS volume attached\n                        and mounted on Kubernetes nodes.\n                      properties:\n                        fsType:\n                          description: fsType is the filesystem type to mount. Must\n                            be a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        readOnly:\n                          description: readOnly defaults to false (read/write). ReadOnly\n                            here will force the ReadOnly setting in VolumeMounts.\n                          type: boolean\n                        secretRef:\n                          description: secretRef specifies the secret to use for obtaining\n                            the StorageOS API credentials.  If not specified, default\n                            values will be attempted.\n                          properties:\n                            name:\n                              description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                TODO: Add other useful fields. apiVersion, kind, uid?'\n                              type: string\n                          type: object\n                          x-kubernetes-map-type: atomic\n                        volumeName:\n                          description: volumeName is the human-readable name of the\n                            StorageOS volume.  Volume names are only unique within\n                            a namespace.\n                          type: string\n                        volumeNamespace:\n                          description: volumeNamespace specifies the scope of the\n                            volume within StorageOS.  If no namespace is specified\n                            then the Pod's namespace will be used.  This allows the\n                            Kubernetes name scoping to be mirrored within StorageOS\n                            for tighter integration. Set VolumeName to any name to\n                            override the default behaviour. Set to \"default\" if you\n                            are not using namespaces within StorageOS. Namespaces\n                            that do not pre-exist within StorageOS will be created.\n                          type: string\n                      type: object\n                    vsphereVolume:\n                      description: vsphereVolume represents a vSphere volume attached\n                        and mounted on kubelets host machine\n                      properties:\n                        fsType:\n                          description: fsType is filesystem type to mount. Must be\n                            a filesystem type supported by the host operating system.\n                            Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\"\n                            if unspecified.\n                          type: string\n                        storagePolicyID:\n                          description: storagePolicyID is the storage Policy Based\n                            Management (SPBM) profile ID associated with the StoragePolicyName.\n                          type: string\n                        storagePolicyName:\n                          description: storagePolicyName is the storage Policy Based\n                            Management (SPBM) profile name.\n                          type: string\n                        volumePath:\n                          description: volumePath is the path that identifies vSphere\n                            volume vmdk\n                          type: string\n                      required:\n                      - volumePath\n                      type: object\n                  required:\n                  - name\n                  type: object\n                type: array\n            type: object\n          status:\n            description: 'Most recent observed status of the ThanosRuler cluster.\n              Read-only. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n            properties:\n              availableReplicas:\n                description: Total number of available pods (ready for at least minReadySeconds)\n                  targeted by this ThanosRuler deployment.\n                format: int32\n                type: integer\n              conditions:\n                description: The current state of the Alertmanager object.\n                items:\n                  description: Condition represents the state of the resources associated\n                    with the Prometheus, Alertmanager or ThanosRuler resource.\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the time of the last update\n                        to the current status property.\n                      format: date-time\n                      type: string\n                    message:\n                      description: Human-readable message indicating details for the\n                        condition's last transition.\n                      type: string\n                    observedGeneration:\n                      description: ObservedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if `.metadata.generation`\n                        is currently 12, but the `.status.conditions[].observedGeneration`\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      type: integer\n                    reason:\n                      description: Reason for the condition's last transition.\n                      type: string\n                    status:\n                      description: Status of the condition.\n                      type: string\n                    type:\n                      description: Type of the condition being reported.\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              paused:\n                description: Represents whether any actions on the underlying managed\n                  objects are being performed. Only delete actions will be performed.\n                type: boolean\n              replicas:\n                description: Total number of non-terminated pods targeted by this\n                  ThanosRuler deployment (their labels match the selector).\n                format: int32\n                type: integer\n              unavailableReplicas:\n                description: Total number of unavailable pods targeted by this ThanosRuler\n                  deployment.\n                format: int32\n                type: integer\n              updatedReplicas:\n                description: Total number of non-terminated pods targeted by this\n                  ThanosRuler deployment that have the desired version spec.\n                format: int32\n                type: integer\n            required:\n            - availableReplicas\n            - paused\n            - replicas\n            - unavailableReplicas\n            - updatedReplicas\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n{{ end }}\n"
  },
  {
    "path": "third_party/kube-prometheus-stack/BUILD.bazel",
    "content": "# https://github.com/helm/charts/blob/master/LICENSE\n# Apache license\nlicenses([\"notice\"])\n\n# this chart was downloaded by running:\n#   helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#   helm fetch prometheus-community/kube-prometheus-stack --version=x.y.z\n# then edit VERSION= in update_crd.sh\n# then run update_crd.sh to generate new 00-crds.yaml\n\nexports_files(\n    glob([\n        \"*.tgz\",\n        \"*.yaml\",\n    ]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "third_party/kube-prometheus-stack/update_crd.sh",
    "content": "#!/bin/bash\n\n# match the version to the app version in this command:\n# helm search repo prometheus-community/kube-prometheus-stack --version='x.y.z'\nVERSION=0.71\n# https://github.com/prometheus-operator/prometheus-operator/tree/main/example/prometheus-operator-crd\n# TODO(ensonic): can we tak this directly from the chart?\nBASEURL=\"https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/release-${VERSION}/example/prometheus-operator-crd/monitoring.coreos.com\"\n\nOUT=00-crds.yaml\ncurl > \"${OUT}\" ${BASEURL}_probes.yaml\n\nOUT=01-crds.yaml\necho '{{ if eq .Values.app_management \"true\" }}' > \"${OUT}\"\nfor CRD in alertmanagerconfigs alertmanagers prometheuses prometheusrules podmonitors scrapeconfigs servicemonitors thanosrulers; do\n  # these already have \"---\" separators\n  curl >> \"${OUT}\" ${BASEURL}_${CRD}.yaml\ndone\necho '{{ end }}' >> \"${OUT}\"\n"
  },
  {
    "path": "third_party/kubernetes_proto/meta/BUILD.bazel",
    "content": "load(\"@com_google_protobuf//bazel:cc_proto_library.bzl\", \"cc_proto_library\")\nload(\"@com_google_protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\nlicenses([\"notice\"])\n\nproto_library(\n    name = \"meta_proto\",\n    srcs = [\"generated.proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//third_party/kubernetes_proto/runtime:runtime_proto\",\n        \"//third_party/kubernetes_proto/schema:schema_proto\",\n        \"@com_google_protobuf//:timestamp_proto\",\n    ],\n)\n\ngo_proto_library(\n    name = \"meta_go_proto\",\n    compilers = [\"@io_bazel_rules_go//proto:go_grpc\"],\n    importpath = \"github.com/googlecloudrobotics/core/third_party/kubernetes_proto/meta\",\n    proto = \":meta_proto\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//third_party/kubernetes_proto/runtime:go_default_library\",\n        \"//third_party/kubernetes_proto/schema:go_default_library\",\n    ],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    embed = [\":meta_go_proto\"],\n    importpath = \"github.com/googlecloudrobotics/core/third_party/kubernetes_proto/meta\",\n    visibility = [\"//visibility:public\"],\n)\n\ncc_proto_library(\n    name = \"meta_cc_proto\",\n    visibility = [\"//visibility:public\"],\n    deps = [\":meta_proto\"],\n)\n"
  },
  {
    "path": "third_party/kubernetes_proto/meta/README.md",
    "content": "# Manual modification of `generated.proto` #\n\nThe file `generated.proto` was modified from its original upstream version in\nthe following way:\n\nAll fields of type `Time` have been replaced with identically named fields of\ntype `google.protobuf.Timestamp`. This modification is necessary to enable\nconversion between proto binary format and JSON without unmarshaling to Go\nstructs as an intermediate step.\n"
  },
  {
    "path": "third_party/kubernetes_proto/meta/generated.proto",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n\n// This file was autogenerated by go-to-protobuf. Do not edit it manually!\n\nsyntax = \"proto2\";\n\npackage k8s.io.apimachinery.pkg.apis.meta.v1;\n\n// --- BEGIN MANUAL EDIT ---\nimport \"third_party/kubernetes_proto/runtime/generated.proto\";\nimport \"third_party/kubernetes_proto/schema/generated.proto\";\n\nimport \"google/protobuf/timestamp.proto\";\n// --- END MANUAL EDIT ---\n\n// Package-wide variables from generator \"generated\".\noption go_package = \"k8s.io/apimachinery/pkg/apis/meta/v1\";\n\n// APIGroup contains the name, the supported versions, and the preferred version\n// of a group.\nmessage APIGroup {\n  // name is the name of the group.\n  optional string name = 1;\n\n  // versions are the versions supported in this group.\n  repeated GroupVersionForDiscovery versions = 2;\n\n  // preferredVersion is the version preferred by the API server, which\n  // probably is the storage version.\n  // +optional\n  optional GroupVersionForDiscovery preferredVersion = 3;\n\n  // a map of client CIDR to server address that is serving this group.\n  // This is to help clients reach servers in the most network-efficient way possible.\n  // Clients can use the appropriate server address as per the CIDR that they match.\n  // In case of multiple matches, clients should use the longest matching CIDR.\n  // The server returns only those CIDRs that it thinks that the client can match.\n  // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.\n  // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.\n  // +optional\n  repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 4;\n}\n\n// APIGroupList is a list of APIGroup, to allow clients to discover the API at\n// /apis.\nmessage APIGroupList {\n  // groups is a list of APIGroup.\n  repeated APIGroup groups = 1;\n}\n\n// APIResource specifies the name of a resource and whether it is namespaced.\nmessage APIResource {\n  // name is the plural name of the resource.\n  optional string name = 1;\n\n  // singularName is the singular name of the resource.  This allows clients to handle plural and singular opaquely.\n  // The singularName is more correct for reporting status on a single item and both singular and plural are allowed\n  // from the kubectl CLI interface.\n  optional string singularName = 6;\n\n  // namespaced indicates if a resource is namespaced or not.\n  optional bool namespaced = 2;\n\n  // group is the preferred group of the resource.  Empty implies the group of the containing resource list.\n  // For subresources, this may have a different value, for example: Scale\".\n  optional string group = 8;\n\n  // version is the preferred version of the resource.  Empty implies the version of the containing resource list\n  // For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".\n  optional string version = 9;\n\n  // kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')\n  optional string kind = 3;\n\n  // verbs is a list of supported kube verbs (this includes get, list, watch, create,\n  // update, patch, delete, deletecollection, and proxy)\n  optional Verbs verbs = 4;\n\n  // shortNames is a list of suggested short names of the resource.\n  repeated string shortNames = 5;\n\n  // categories is a list of the grouped resources this resource belongs to (e.g. 'all')\n  repeated string categories = 7;\n\n  // The hash value of the storage version, the version this resource is\n  // converted to when written to the data store. Value must be treated\n  // as opaque by clients. Only equality comparison on the value is valid.\n  // This is an alpha feature and may change or be removed in the future.\n  // The field is populated by the apiserver only if the\n  // StorageVersionHash feature gate is enabled.\n  // This field will remain optional even if it graduates.\n  // +optional\n  optional string storageVersionHash = 10;\n}\n\n// APIResourceList is a list of APIResource, it is used to expose the name of the\n// resources supported in a specific group and version, and if the resource\n// is namespaced.\nmessage APIResourceList {\n  // groupVersion is the group and version this APIResourceList is for.\n  optional string groupVersion = 1;\n\n  // resources contains the name of the resources and if they are namespaced.\n  repeated APIResource resources = 2;\n}\n\n// APIVersions lists the versions that are available, to allow clients to\n// discover the API at /api, which is the root path of the legacy v1 API.\n//\n// +protobuf.options.(gogoproto.goproto_stringer)=false\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\nmessage APIVersions {\n  // versions are the api versions that are available.\n  repeated string versions = 1;\n\n  // a map of client CIDR to server address that is serving this group.\n  // This is to help clients reach servers in the most network-efficient way possible.\n  // Clients can use the appropriate server address as per the CIDR that they match.\n  // In case of multiple matches, clients should use the longest matching CIDR.\n  // The server returns only those CIDRs that it thinks that the client can match.\n  // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.\n  // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.\n  repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 2;\n}\n\n// ApplyOptions may be provided when applying an API object.\n// FieldManager is required for apply requests.\n// ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation\n// that speaks specifically to how the options fields relate to apply.\nmessage ApplyOptions {\n  // When present, indicates that modifications should not be\n  // persisted. An invalid or unrecognized dryRun directive will\n  // result in an error response and no further processing of the\n  // request. Valid values are:\n  // - All: all dry run stages will be processed\n  // +optional\n  repeated string dryRun = 1;\n\n  // Force is going to \"force\" Apply requests. It means user will\n  // re-acquire conflicting fields owned by other people.\n  optional bool force = 2;\n\n  // fieldManager is a name associated with the actor or entity\n  // that is making these changes. The value must be less than or\n  // 128 characters long, and only contain printable characters,\n  // as defined by https://golang.org/pkg/unicode/#IsPrint. This\n  // field is required.\n  optional string fieldManager = 3;\n}\n\n// Condition contains details for one aspect of the current state of this API Resource.\n// ---\n// This struct is intended for direct use as an array at the field path .status.conditions.  For example,\n//\n// \ttype FooStatus struct{\n// \t    // Represents the observations of a foo's current state.\n// \t    // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\"\n// \t    // +patchMergeKey=type\n// \t    // +patchStrategy=merge\n// \t    // +listType=map\n// \t    // +listMapKey=type\n// \t    Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n//\n// \t    // other fields\n// \t}\nmessage Condition {\n  // type of condition in CamelCase or in foo.example.com/CamelCase.\n  // ---\n  // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n  // useful (see .node.status.conditions), the ability to deconflict is important.\n  // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n  // +required\n  // +kubebuilder:validation:Required\n  // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$`\n  // +kubebuilder:validation:MaxLength=316\n  optional string type = 1;\n\n  // status of the condition, one of True, False, Unknown.\n  // +required\n  // +kubebuilder:validation:Required\n  // +kubebuilder:validation:Enum=True;False;Unknown\n  optional string status = 2;\n\n  // observedGeneration represents the .metadata.generation that the condition was set based upon.\n  // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n  // with respect to the current state of the instance.\n  // +optional\n  // +kubebuilder:validation:Minimum=0\n  optional int64 observedGeneration = 3;\n\n  // lastTransitionTime is the last time the condition transitioned from one status to another.\n  // This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n  // +required\n  // +kubebuilder:validation:Required\n  // +kubebuilder:validation:Type=string\n  // +kubebuilder:validation:Format=date-time\n\n  // --- BEGIN MANUAL EDIT ---\n  //optional Time lastTransitionTime = 4;\n  optional google.protobuf.Timestamp lastTransitionTime = 4;\n  // --- END MANUAL EDIT ---\n\n  // reason contains a programmatic identifier indicating the reason for the condition's last transition.\n  // Producers of specific condition types may define expected values and meanings for this field,\n  // and whether the values are considered a guaranteed API.\n  // The value should be a CamelCase string.\n  // This field may not be empty.\n  // +required\n  // +kubebuilder:validation:Required\n  // +kubebuilder:validation:MaxLength=1024\n  // +kubebuilder:validation:MinLength=1\n  // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`\n  optional string reason = 5;\n\n  // message is a human readable message indicating details about the transition.\n  // This may be an empty string.\n  // +required\n  // +kubebuilder:validation:Required\n  // +kubebuilder:validation:MaxLength=32768\n  optional string message = 6;\n}\n\n// CreateOptions may be provided when creating an API object.\nmessage CreateOptions {\n  // When present, indicates that modifications should not be\n  // persisted. An invalid or unrecognized dryRun directive will\n  // result in an error response and no further processing of the\n  // request. Valid values are:\n  // - All: all dry run stages will be processed\n  // +optional\n  repeated string dryRun = 1;\n\n  // fieldManager is a name associated with the actor or entity\n  // that is making these changes. The value must be less than or\n  // 128 characters long, and only contain printable characters,\n  // as defined by https://golang.org/pkg/unicode/#IsPrint.\n  // +optional\n  optional string fieldManager = 3;\n\n  // fieldValidation instructs the server on how to handle\n  // objects in the request (POST/PUT/PATCH) containing unknown\n  // or duplicate fields, provided that the `ServerSideFieldValidation`\n  // feature gate is also enabled. Valid values are:\n  // - Ignore: This will ignore any unknown fields that are silently\n  // dropped from the object, and will ignore all but the last duplicate\n  // field that the decoder encounters. This is the default behavior\n  // prior to v1.23 and is the default behavior when the\n  // `ServerSideFieldValidation` feature gate is disabled.\n  // - Warn: This will send a warning via the standard warning response\n  // header for each unknown field that is dropped from the object, and\n  // for each duplicate field that is encountered. The request will\n  // still succeed if there are no other errors, and will only persist\n  // the last of any duplicate fields. This is the default when the\n  // `ServerSideFieldValidation` feature gate is enabled.\n  // - Strict: This will fail the request with a BadRequest error if\n  // any unknown fields would be dropped from the object, or if any\n  // duplicate fields are present. The error returned from the server\n  // will contain all unknown and duplicate fields encountered.\n  // +optional\n  optional string fieldValidation = 4;\n}\n\n// DeleteOptions may be provided when deleting an API object.\nmessage DeleteOptions {\n  // The duration in seconds before the object should be deleted. Value must be non-negative integer.\n  // The value zero indicates delete immediately. If this value is nil, the default grace period for the\n  // specified type will be used.\n  // Defaults to a per object value if not specified. zero means delete immediately.\n  // +optional\n  optional int64 gracePeriodSeconds = 1;\n\n  // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be\n  // returned.\n  // +k8s:conversion-gen=false\n  // +optional\n  optional Preconditions preconditions = 2;\n\n  // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7.\n  // Should the dependent objects be orphaned. If true/false, the \"orphan\"\n  // finalizer will be added to/removed from the object's finalizers list.\n  // Either this field or PropagationPolicy may be set, but not both.\n  // +optional\n  optional bool orphanDependents = 3;\n\n  // Whether and how garbage collection will be performed.\n  // Either this field or OrphanDependents may be set, but not both.\n  // The default policy is decided by the existing finalizer set in the\n  // metadata.finalizers and the resource-specific default policy.\n  // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' -\n  // allow the garbage collector to delete the dependents in the background;\n  // 'Foreground' - a cascading policy that deletes all dependents in the\n  // foreground.\n  // +optional\n  optional string propagationPolicy = 4;\n\n  // When present, indicates that modifications should not be\n  // persisted. An invalid or unrecognized dryRun directive will\n  // result in an error response and no further processing of the\n  // request. Valid values are:\n  // - All: all dry run stages will be processed\n  // +optional\n  repeated string dryRun = 5;\n}\n\n// Duration is a wrapper around time.Duration which supports correct\n// marshaling to YAML and JSON. In particular, it marshals into strings, which\n// can be used as map keys in json.\nmessage Duration {\n  optional int64 duration = 1;\n}\n\n// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n//\n// Each key is either a '.' representing the field itself, and will always map to an empty set,\n// or a string representing a sub-field or item. The string will follow one of these four formats:\n// 'f:<name>', where <name> is the name of a field in a struct, or key in a map\n// 'v:<value>', where <value> is the exact json formatted value of a list item\n// 'i:<index>', where <index> is position of a item in a list\n// 'k:<keys>', where <keys> is a map of  a list item's key fields to their unique values\n// If a key maps to an empty Fields value, the field that key represents is part of the set.\n//\n// The exact format is defined in sigs.k8s.io/structured-merge-diff\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage FieldsV1 {\n  // Raw is the underlying serialization of this object.\n  optional bytes Raw = 1;\n}\n\n// GetOptions is the standard query options to the standard REST get call.\nmessage GetOptions {\n  // resourceVersion sets a constraint on what resource versions a request may be served from.\n  // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for\n  // details.\n  //\n  // Defaults to unset\n  // +optional\n  optional string resourceVersion = 1;\n}\n\n// GroupKind specifies a Group and a Kind, but does not force a version.  This is useful for identifying\n// concepts during lookup stages without having partially valid types\n//\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage GroupKind {\n  optional string group = 1;\n\n  optional string kind = 2;\n}\n\n// GroupResource specifies a Group and a Resource, but does not force a version.  This is useful for identifying\n// concepts during lookup stages without having partially valid types\n//\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage GroupResource {\n  optional string group = 1;\n\n  optional string resource = 2;\n}\n\n// GroupVersion contains the \"group\" and the \"version\", which uniquely identifies the API.\n//\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage GroupVersion {\n  optional string group = 1;\n\n  optional string version = 2;\n}\n\n// GroupVersion contains the \"group/version\" and \"version\" string of a version.\n// It is made a struct to keep extensibility.\nmessage GroupVersionForDiscovery {\n  // groupVersion specifies the API group and version in the form \"group/version\"\n  optional string groupVersion = 1;\n\n  // version specifies the version in the form of \"version\". This is to save\n  // the clients the trouble of splitting the GroupVersion.\n  optional string version = 2;\n}\n\n// GroupVersionKind unambiguously identifies a kind.  It doesn't anonymously include GroupVersion\n// to avoid automatic coercion.  It doesn't use a GroupVersion to avoid custom marshalling\n//\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage GroupVersionKind {\n  optional string group = 1;\n\n  optional string version = 2;\n\n  optional string kind = 3;\n}\n\n// GroupVersionResource unambiguously identifies a resource.  It doesn't anonymously include GroupVersion\n// to avoid automatic coercion.  It doesn't use a GroupVersion to avoid custom marshalling\n//\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage GroupVersionResource {\n  optional string group = 1;\n\n  optional string version = 2;\n\n  optional string resource = 3;\n}\n\n// A label selector is a label query over a set of resources. The result of matchLabels and\n// matchExpressions are ANDed. An empty label selector matches all objects. A null\n// label selector matches no objects.\n// +structType=atomic\nmessage LabelSelector {\n  // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n  // map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n  // operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n  // +optional\n  map<string, string> matchLabels = 1;\n\n  // matchExpressions is a list of label selector requirements. The requirements are ANDed.\n  // +optional\n  repeated LabelSelectorRequirement matchExpressions = 2;\n}\n\n// A label selector requirement is a selector that contains values, a key, and an operator that\n// relates the key and values.\nmessage LabelSelectorRequirement {\n  // key is the label key that the selector applies to.\n  // +patchMergeKey=key\n  // +patchStrategy=merge\n  optional string key = 1;\n\n  // operator represents a key's relationship to a set of values.\n  // Valid operators are In, NotIn, Exists and DoesNotExist.\n  optional string operator = 2;\n\n  // values is an array of string values. If the operator is In or NotIn,\n  // the values array must be non-empty. If the operator is Exists or DoesNotExist,\n  // the values array must be empty. This array is replaced during a strategic\n  // merge patch.\n  // +optional\n  repeated string values = 3;\n}\n\n// List holds a list of objects, which may not be known by the server.\nmessage List {\n  // Standard list metadata.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n  // +optional\n  optional ListMeta metadata = 1;\n\n  // List of objects\n  repeated k8s.io.apimachinery.pkg.runtime.RawExtension items = 2;\n}\n\n// ListMeta describes metadata that synthetic resources must have, including lists and\n// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.\nmessage ListMeta {\n  // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.\n  // +optional\n  optional string selfLink = 1;\n\n  // String that identifies the server's internal version of this object that\n  // can be used by clients to determine when objects have changed.\n  // Value must be treated as opaque by clients and passed unmodified back to the server.\n  // Populated by the system.\n  // Read-only.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency\n  // +optional\n  optional string resourceVersion = 2;\n\n  // continue may be set if the user set a limit on the number of items returned, and indicates that\n  // the server has more data available. The value is opaque and may be used to issue another request\n  // to the endpoint that served this list to retrieve the next set of available objects. Continuing a\n  // consistent list may not be possible if the server configuration has changed or more than a few\n  // minutes have passed. The resourceVersion field returned when using this continue value will be\n  // identical to the value in the first response, unless you have received this token from an error\n  // message.\n  optional string continue = 3;\n\n  // remainingItemCount is the number of subsequent items in the list which are not included in this\n  // list response. If the list request contained label or field selectors, then the number of\n  // remaining items is unknown and the field will be left unset and omitted during serialization.\n  // If the list is complete (either because it is not chunking or because this is the last chunk),\n  // then there are no more remaining items and this field will be left unset and omitted during\n  // serialization.\n  // Servers older than v1.15 do not set this field.\n  // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients\n  // should not rely on the remainingItemCount to be set or to be exact.\n  // +optional\n  optional int64 remainingItemCount = 4;\n}\n\n// ListOptions is the query options to a standard REST list call.\nmessage ListOptions {\n  // A selector to restrict the list of returned objects by their labels.\n  // Defaults to everything.\n  // +optional\n  optional string labelSelector = 1;\n\n  // A selector to restrict the list of returned objects by their fields.\n  // Defaults to everything.\n  // +optional\n  optional string fieldSelector = 2;\n\n  // Watch for changes to the described resources and return them as a stream of\n  // add, update, and remove notifications. Specify resourceVersion.\n  // +optional\n  optional bool watch = 3;\n\n  // allowWatchBookmarks requests watch events with type \"BOOKMARK\".\n  // Servers that do not implement bookmarks may ignore this flag and\n  // bookmarks are sent at the server's discretion. Clients should not\n  // assume bookmarks are returned at any specific interval, nor may they\n  // assume the server will send any BOOKMARK event during a session.\n  // If this is not a watch, this field is ignored.\n  // +optional\n  optional bool allowWatchBookmarks = 9;\n\n  // resourceVersion sets a constraint on what resource versions a request may be served from.\n  // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for\n  // details.\n  //\n  // Defaults to unset\n  // +optional\n  optional string resourceVersion = 4;\n\n  // resourceVersionMatch determines how resourceVersion is applied to list calls.\n  // It is highly recommended that resourceVersionMatch be set for list calls where\n  // resourceVersion is set\n  // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for\n  // details.\n  //\n  // Defaults to unset\n  // +optional\n  optional string resourceVersionMatch = 10;\n\n  // Timeout for the list/watch call.\n  // This limits the duration of the call, regardless of any activity or inactivity.\n  // +optional\n  optional int64 timeoutSeconds = 5;\n\n  // limit is a maximum number of responses to return for a list call. If more items exist, the\n  // server will set the `continue` field on the list metadata to a value that can be used with the\n  // same initial query to retrieve the next set of results. Setting a limit may return fewer than\n  // the requested amount of items (up to zero items) in the event all requested objects are\n  // filtered out and clients should only use the presence of the continue field to determine whether\n  // more results are available. Servers may choose not to support the limit argument and will return\n  // all of the available results. If limit is specified and the continue field is empty, clients may\n  // assume that no more results are available. This field is not supported if watch is true.\n  //\n  // The server guarantees that the objects returned when using continue will be identical to issuing\n  // a single list call without a limit - that is, no objects created, modified, or deleted after the\n  // first request is issued will be included in any subsequent continued requests. This is sometimes\n  // referred to as a consistent snapshot, and ensures that a client that is using limit to receive\n  // smaller chunks of a very large result can ensure they see all possible objects. If objects are\n  // updated during a chunked list the version of the object that was present at the time the first list\n  // result was calculated is returned.\n  optional int64 limit = 7;\n\n  // The continue option should be set when retrieving more results from the server. Since this value is\n  // server defined, clients may only use the continue value from a previous query result with identical\n  // query parameters (except for the value of continue) and the server may reject a continue value it\n  // does not recognize. If the specified continue value is no longer valid whether due to expiration\n  // (generally five to fifteen minutes) or a configuration change on the server, the server will\n  // respond with a 410 ResourceExpired error together with a continue token. If the client needs a\n  // consistent list, it must restart their list without the continue field. Otherwise, the client may\n  // send another list request with the token received with the 410 error, the server will respond with\n  // a list starting from the next key, but from the latest snapshot, which is inconsistent from the\n  // previous list results - objects that are created, modified, or deleted after the first list request\n  // will be included in the response, as long as their keys are after the \"next key\".\n  //\n  // This field is not supported when watch is true. Clients may start a watch from the last\n  // resourceVersion value returned by the server and not miss any modifications.\n  optional string continue = 8;\n}\n\n// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource\n// that the fieldset applies to.\nmessage ManagedFieldsEntry {\n  // Manager is an identifier of the workflow managing these fields.\n  optional string manager = 1;\n\n  // Operation is the type of operation which lead to this ManagedFieldsEntry being created.\n  // The only valid values for this field are 'Apply' and 'Update'.\n  optional string operation = 2;\n\n  // APIVersion defines the version of this resource that this field set\n  // applies to. The format is \"group/version\" just like the top-level\n  // APIVersion field. It is necessary to track the version of a field\n  // set because it cannot be automatically converted.\n  optional string apiVersion = 3;\n\n  // Time is the timestamp of when the ManagedFields entry was added. The\n  // timestamp will also be updated if a field is added, the manager\n  // changes any of the owned fields value or removes a field. The\n  // timestamp does not update when a field is removed from the entry\n  // because another manager took it over.\n  // +optional\n  // --- BEGIN MANUAL EDIT ---\n  //optional Time time = 4;\n  optional google.protobuf.Timestamp time = 4;\n  // --- END MANUAL EDIT ---\n\n  // FieldsType is the discriminator for the different fields format and version.\n  // There is currently only one possible value: \"FieldsV1\"\n  optional string fieldsType = 6;\n\n  // FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.\n  // +optional\n  optional FieldsV1 fieldsV1 = 7;\n\n  // Subresource is the name of the subresource used to update that object, or\n  // empty string if the object was updated through the main resource. The\n  // value of this field is used to distinguish between managers, even if they\n  // share the same name. For example, a status update will be distinct from a\n  // regular update using the same manager name.\n  // Note that the APIVersion field is not related to the Subresource field and\n  // it always corresponds to the version of the main resource.\n  optional string subresource = 8;\n}\n\n// MicroTime is version of Time with microsecond level precision.\n//\n// +protobuf.options.marshal=false\n// +protobuf.as=Timestamp\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage MicroTime {\n  // Represents seconds of UTC time since Unix epoch\n  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n  // 9999-12-31T23:59:59Z inclusive.\n  optional int64 seconds = 1;\n\n  // Non-negative fractions of a second at nanosecond resolution. Negative\n  // second values with fractions must still have non-negative nanos values\n  // that count forward in time. Must be from 0 to 999,999,999\n  // inclusive. This field may be limited in precision depending on context.\n  optional int32 nanos = 2;\n}\n\n// ObjectMeta is metadata that all persisted resources must have, which includes all objects\n// users must create.\nmessage ObjectMeta {\n  // Name must be unique within a namespace. Is required when creating resources, although\n  // some resources may allow a client to request the generation of an appropriate name\n  // automatically. Name is primarily intended for creation idempotence and configuration\n  // definition.\n  // Cannot be updated.\n  // More info: http://kubernetes.io/docs/user-guide/identifiers#names\n  // +optional\n  optional string name = 1;\n\n  // GenerateName is an optional prefix, used by the server, to generate a unique\n  // name ONLY IF the Name field has not been provided.\n  // If this field is used, the name returned to the client will be different\n  // than the name passed. This value will also be combined with a unique suffix.\n  // The provided value has the same validation rules as the Name field,\n  // and may be truncated by the length of the suffix required to make the value\n  // unique on the server.\n  //\n  // If this field is specified and the generated name exists, the server will return a 409.\n  //\n  // Applied only if Name is not specified.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency\n  // +optional\n  optional string generateName = 2;\n\n  // Namespace defines the space within which each name must be unique. An empty namespace is\n  // equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n  // Not all objects are required to be scoped to a namespace - the value of this field for\n  // those objects will be empty.\n  //\n  // Must be a DNS_LABEL.\n  // Cannot be updated.\n  // More info: http://kubernetes.io/docs/user-guide/namespaces\n  // +optional\n  optional string namespace = 3;\n\n  // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.\n  // +optional\n  optional string selfLink = 4;\n\n  // UID is the unique in time and space value for this object. It is typically generated by\n  // the server on successful creation of a resource and is not allowed to change on PUT\n  // operations.\n  //\n  // Populated by the system.\n  // Read-only.\n  // More info: http://kubernetes.io/docs/user-guide/identifiers#uids\n  // +optional\n  optional string uid = 5;\n\n  // An opaque value that represents the internal version of this object that can\n  // be used by clients to determine when objects have changed. May be used for optimistic\n  // concurrency, change detection, and the watch operation on a resource or set of resources.\n  // Clients must treat these values as opaque and passed unmodified back to the server.\n  // They may only be valid for a particular resource or set of resources.\n  //\n  // Populated by the system.\n  // Read-only.\n  // Value must be treated as opaque by clients and .\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency\n  // +optional\n  optional string resourceVersion = 6;\n\n  // A sequence number representing a specific generation of the desired state.\n  // Populated by the system. Read-only.\n  // +optional\n  optional int64 generation = 7;\n\n  // CreationTimestamp is a timestamp representing the server time when this object was\n  // created. It is not guaranteed to be set in happens-before order across separate operations.\n  // Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n  //\n  // Populated by the system.\n  // Read-only.\n  // Null for lists.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata\n  // +optional\n  // --- BEGIN MANUAL EDIT ---\n  //optional Time creationTimestamp = 8;\n  optional google.protobuf.Timestamp creationTimestamp = 8;\n  // --- END MANUAL EDIT ---\n\n  // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This\n  // field is set by the server when a graceful deletion is requested by the user, and is not\n  // directly settable by a client. The resource is expected to be deleted (no longer visible\n  // from resource lists, and not reachable by name) after the time in this field, once the\n  // finalizers list is empty. As long as the finalizers list contains items, deletion is blocked.\n  // Once the deletionTimestamp is set, this value may not be unset or be set further into the\n  // future, although it may be shortened or the resource may be deleted prior to this time.\n  // For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react\n  // by sending a graceful termination signal to the containers in the pod. After that 30 seconds,\n  // the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup,\n  // remove the pod from the API. In the presence of network partitions, this object may still\n  // exist after this timestamp, until an administrator or automated process can determine the\n  // resource is fully terminated.\n  // If not set, graceful deletion of the object has not been requested.\n  //\n  // Populated by the system when a graceful deletion is requested.\n  // Read-only.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata\n  // +optional\n  // --- BEGIN MANUAL EDIT ---\n  //optional Time deletionTimestamp = 9;\n  optional google.protobuf.Timestamp deletionTimestamp = 9;\n  // --- END MANUAL EDIT ---\n\n  // Number of seconds allowed for this object to gracefully terminate before\n  // it will be removed from the system. Only set when deletionTimestamp is also set.\n  // May only be shortened.\n  // Read-only.\n  // +optional\n  optional int64 deletionGracePeriodSeconds = 10;\n\n  // Map of string keys and values that can be used to organize and categorize\n  // (scope and select) objects. May match selectors of replication controllers\n  // and services.\n  // More info: http://kubernetes.io/docs/user-guide/labels\n  // +optional\n  map<string, string> labels = 11;\n\n  // Annotations is an unstructured key value map stored with a resource that may be\n  // set by external tools to store and retrieve arbitrary metadata. They are not\n  // queryable and should be preserved when modifying objects.\n  // More info: http://kubernetes.io/docs/user-guide/annotations\n  // +optional\n  map<string, string> annotations = 12;\n\n  // List of objects depended by this object. If ALL objects in the list have\n  // been deleted, this object will be garbage collected. If this object is managed by a controller,\n  // then an entry in this list will point to this controller, with the controller field set to true.\n  // There cannot be more than one managing controller.\n  // +optional\n  // +patchMergeKey=uid\n  // +patchStrategy=merge\n  repeated OwnerReference ownerReferences = 13;\n\n  // Must be empty before the object is deleted from the registry. Each entry\n  // is an identifier for the responsible component that will remove the entry\n  // from the list. If the deletionTimestamp of the object is non-nil, entries\n  // in this list can only be removed.\n  // Finalizers may be processed and removed in any order.  Order is NOT enforced\n  // because it introduces significant risk of stuck finalizers.\n  // finalizers is a shared field, any actor with permission can reorder it.\n  // If the finalizer list is processed in order, then this can lead to a situation\n  // in which the component responsible for the first finalizer in the list is\n  // waiting for a signal (field value, external system, or other) produced by a\n  // component responsible for a finalizer later in the list, resulting in a deadlock.\n  // Without enforced ordering finalizers are free to order amongst themselves and\n  // are not vulnerable to ordering changes in the list.\n  // +optional\n  // +patchStrategy=merge\n  repeated string finalizers = 14;\n\n  // ManagedFields maps workflow-id and version to the set of fields\n  // that are managed by that workflow. This is mostly for internal\n  // housekeeping, and users typically shouldn't need to set or\n  // understand this field. A workflow can be the user's name, a\n  // controller's name, or the name of a specific apply path like\n  // \"ci-cd\". The set of fields is always in the version that the\n  // workflow used when modifying the object.\n  //\n  // +optional\n  repeated ManagedFieldsEntry managedFields = 17;\n}\n\n// OwnerReference contains enough information to let you identify an owning\n// object. An owning object must be in the same namespace as the dependent, or\n// be cluster-scoped, so there is no namespace field.\n// +structType=atomic\nmessage OwnerReference {\n  // API version of the referent.\n  optional string apiVersion = 5;\n\n  // Kind of the referent.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n  optional string kind = 1;\n\n  // Name of the referent.\n  // More info: http://kubernetes.io/docs/user-guide/identifiers#names\n  optional string name = 3;\n\n  // UID of the referent.\n  // More info: http://kubernetes.io/docs/user-guide/identifiers#uids\n  optional string uid = 4;\n\n  // If true, this reference points to the managing controller.\n  // +optional\n  optional bool controller = 6;\n\n  // If true, AND if the owner has the \"foregroundDeletion\" finalizer, then\n  // the owner cannot be deleted from the key-value store until this\n  // reference is removed.\n  // See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion\n  // for how the garbage collector interacts with this field and enforces the foreground deletion.\n  // Defaults to false.\n  // To set this field, a user needs \"delete\" permission of the owner,\n  // otherwise 422 (Unprocessable Entity) will be returned.\n  // +optional\n  optional bool blockOwnerDeletion = 7;\n}\n\n// PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients\n// to get access to a particular ObjectMeta schema without knowing the details of the version.\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\nmessage PartialObjectMetadata {\n  // Standard object's metadata.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata\n  // +optional\n  optional ObjectMeta metadata = 1;\n}\n\n// PartialObjectMetadataList contains a list of objects containing only their metadata\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\nmessage PartialObjectMetadataList {\n  // Standard list metadata.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n  // +optional\n  optional ListMeta metadata = 1;\n\n  // items contains each of the included items.\n  repeated PartialObjectMetadata items = 2;\n}\n\n// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.\nmessage Patch {\n}\n\n// PatchOptions may be provided when patching an API object.\n// PatchOptions is meant to be a superset of UpdateOptions.\nmessage PatchOptions {\n  // When present, indicates that modifications should not be\n  // persisted. An invalid or unrecognized dryRun directive will\n  // result in an error response and no further processing of the\n  // request. Valid values are:\n  // - All: all dry run stages will be processed\n  // +optional\n  repeated string dryRun = 1;\n\n  // Force is going to \"force\" Apply requests. It means user will\n  // re-acquire conflicting fields owned by other people. Force\n  // flag must be unset for non-apply patch requests.\n  // +optional\n  optional bool force = 2;\n\n  // fieldManager is a name associated with the actor or entity\n  // that is making these changes. The value must be less than or\n  // 128 characters long, and only contain printable characters,\n  // as defined by https://golang.org/pkg/unicode/#IsPrint. This\n  // field is required for apply requests\n  // (application/apply-patch) but optional for non-apply patch\n  // types (JsonPatch, MergePatch, StrategicMergePatch).\n  // +optional\n  optional string fieldManager = 3;\n\n  // fieldValidation instructs the server on how to handle\n  // objects in the request (POST/PUT/PATCH) containing unknown\n  // or duplicate fields, provided that the `ServerSideFieldValidation`\n  // feature gate is also enabled. Valid values are:\n  // - Ignore: This will ignore any unknown fields that are silently\n  // dropped from the object, and will ignore all but the last duplicate\n  // field that the decoder encounters. This is the default behavior\n  // prior to v1.23 and is the default behavior when the\n  // `ServerSideFieldValidation` feature gate is disabled.\n  // - Warn: This will send a warning via the standard warning response\n  // header for each unknown field that is dropped from the object, and\n  // for each duplicate field that is encountered. The request will\n  // still succeed if there are no other errors, and will only persist\n  // the last of any duplicate fields. This is the default when the\n  // `ServerSideFieldValidation` feature gate is enabled.\n  // - Strict: This will fail the request with a BadRequest error if\n  // any unknown fields would be dropped from the object, or if any\n  // duplicate fields are present. The error returned from the server\n  // will contain all unknown and duplicate fields encountered.\n  // +optional\n  optional string fieldValidation = 4;\n}\n\n// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.\nmessage Preconditions {\n  // Specifies the target UID.\n  // +optional\n  optional string uid = 1;\n\n  // Specifies the target ResourceVersion\n  // +optional\n  optional string resourceVersion = 2;\n}\n\n// RootPaths lists the paths available at root.\n// For example: \"/healthz\", \"/apis\".\nmessage RootPaths {\n  // paths are the paths available at root.\n  repeated string paths = 1;\n}\n\n// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.\nmessage ServerAddressByClientCIDR {\n  // The CIDR with which clients can match their IP to figure out the server address that they should use.\n  optional string clientCIDR = 1;\n\n  // Address of this server, suitable for a client that matches the above CIDR.\n  // This can be a hostname, hostname:port, IP or IP:port.\n  optional string serverAddress = 2;\n}\n\n// Status is a return value for calls that don't return other objects.\nmessage Status {\n  // Standard list metadata.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n  // +optional\n  optional ListMeta metadata = 1;\n\n  // Status of the operation.\n  // One of: \"Success\" or \"Failure\".\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status\n  // +optional\n  optional string status = 2;\n\n  // A human-readable description of the status of this operation.\n  // +optional\n  optional string message = 3;\n\n  // A machine-readable description of why this operation is in the\n  // \"Failure\" status. If this value is empty there\n  // is no information available. A Reason clarifies an HTTP status\n  // code but does not override it.\n  // +optional\n  optional string reason = 4;\n\n  // Extended data associated with the reason.  Each reason may define its\n  // own extended details. This field is optional and the data returned\n  // is not guaranteed to conform to any schema except that defined by\n  // the reason type.\n  // +optional\n  optional StatusDetails details = 5;\n\n  // Suggested HTTP return code for this status, 0 if not set.\n  // +optional\n  optional int32 code = 6;\n}\n\n// StatusCause provides more information about an api.Status failure, including\n// cases when multiple errors are encountered.\nmessage StatusCause {\n  // A machine-readable description of the cause of the error. If this value is\n  // empty there is no information available.\n  // +optional\n  optional string reason = 1;\n\n  // A human-readable description of the cause of the error.  This field may be\n  // presented as-is to a reader.\n  // +optional\n  optional string message = 2;\n\n  // The field of the resource that has caused this error, as named by its JSON\n  // serialization. May include dot and postfix notation for nested attributes.\n  // Arrays are zero-indexed.  Fields may appear more than once in an array of\n  // causes due to fields having multiple errors.\n  // Optional.\n  //\n  // Examples:\n  //   \"name\" - the field \"name\" on the current resource\n  //   \"items[0].name\" - the field \"name\" on the first array entry in \"items\"\n  // +optional\n  optional string field = 3;\n}\n\n// StatusDetails is a set of additional properties that MAY be set by the\n// server to provide additional information about a response. The Reason\n// field of a Status object defines what attributes will be set. Clients\n// must ignore fields that do not match the defined type of each attribute,\n// and should assume that any attribute may be empty, invalid, or under\n// defined.\nmessage StatusDetails {\n  // The name attribute of the resource associated with the status StatusReason\n  // (when there is a single name which can be described).\n  // +optional\n  optional string name = 1;\n\n  // The group attribute of the resource associated with the status StatusReason.\n  // +optional\n  optional string group = 2;\n\n  // The kind attribute of the resource associated with the status StatusReason.\n  // On some operations may differ from the requested resource Kind.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n  // +optional\n  optional string kind = 3;\n\n  // UID of the resource.\n  // (when there is a single resource which can be described).\n  // More info: http://kubernetes.io/docs/user-guide/identifiers#uids\n  // +optional\n  optional string uid = 6;\n\n  // The Causes array includes more details associated with the StatusReason\n  // failure. Not all StatusReasons may provide detailed causes.\n  // +optional\n  repeated StatusCause causes = 4;\n\n  // If specified, the time in seconds before the operation should be retried. Some errors may indicate\n  // the client must take an alternate action - for those errors this field may indicate how long to wait\n  // before taking the alternate action.\n  // +optional\n  optional int32 retryAfterSeconds = 5;\n}\n\n// TableOptions are used when a Table is requested by the caller.\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\nmessage TableOptions {\n  // includeObject decides whether to include each object along with its columnar information.\n  // Specifying \"None\" will return no object, specifying \"Object\" will return the full object contents, and\n  // specifying \"Metadata\" (the default) will return the object's metadata in the PartialObjectMetadata kind\n  // in version v1beta1 of the meta.k8s.io API group.\n  optional string includeObject = 1;\n}\n\n// Time is a wrapper around time.Time which supports correct\n// marshaling to YAML and JSON.  Wrappers are provided for many\n// of the factory methods that the time package offers.\n//\n// +protobuf.options.marshal=false\n// +protobuf.as=Timestamp\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage Time {\n  // Represents seconds of UTC time since Unix epoch\n  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n  // 9999-12-31T23:59:59Z inclusive.\n  optional int64 seconds = 1;\n\n  // Non-negative fractions of a second at nanosecond resolution. Negative\n  // second values with fractions must still have non-negative nanos values\n  // that count forward in time. Must be from 0 to 999,999,999\n  // inclusive. This field may be limited in precision depending on context.\n  optional int32 nanos = 2;\n}\n\n// Timestamp is a struct that is equivalent to Time, but intended for\n// protobuf marshalling/unmarshalling. It is generated into a serialization\n// that matches Time. Do not use in Go structs.\nmessage Timestamp {\n  // Represents seconds of UTC time since Unix epoch\n  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n  // 9999-12-31T23:59:59Z inclusive.\n  optional int64 seconds = 1;\n\n  // Non-negative fractions of a second at nanosecond resolution. Negative\n  // second values with fractions must still have non-negative nanos values\n  // that count forward in time. Must be from 0 to 999,999,999\n  // inclusive. This field may be limited in precision depending on context.\n  optional int32 nanos = 2;\n}\n\n// TypeMeta describes an individual object in an API response or request\n// with strings representing the type of the object and its API schema version.\n// Structures that are versioned or persisted should inline TypeMeta.\n//\n// +k8s:deepcopy-gen=false\nmessage TypeMeta {\n  // Kind is a string value representing the REST resource this object represents.\n  // Servers may infer this from the endpoint the client submits requests to.\n  // Cannot be updated.\n  // In CamelCase.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n  // +optional\n  optional string kind = 1;\n\n  // APIVersion defines the versioned schema of this representation of an object.\n  // Servers should convert recognized schemas to the latest internal value, and\n  // may reject unrecognized values.\n  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n  // +optional\n  optional string apiVersion = 2;\n}\n\n// UpdateOptions may be provided when updating an API object.\n// All fields in UpdateOptions should also be present in PatchOptions.\nmessage UpdateOptions {\n  // When present, indicates that modifications should not be\n  // persisted. An invalid or unrecognized dryRun directive will\n  // result in an error response and no further processing of the\n  // request. Valid values are:\n  // - All: all dry run stages will be processed\n  // +optional\n  repeated string dryRun = 1;\n\n  // fieldManager is a name associated with the actor or entity\n  // that is making these changes. The value must be less than or\n  // 128 characters long, and only contain printable characters,\n  // as defined by https://golang.org/pkg/unicode/#IsPrint.\n  // +optional\n  optional string fieldManager = 2;\n\n  // fieldValidation instructs the server on how to handle\n  // objects in the request (POST/PUT/PATCH) containing unknown\n  // or duplicate fields, provided that the `ServerSideFieldValidation`\n  // feature gate is also enabled. Valid values are:\n  // - Ignore: This will ignore any unknown fields that are silently\n  // dropped from the object, and will ignore all but the last duplicate\n  // field that the decoder encounters. This is the default behavior\n  // prior to v1.23 and is the default behavior when the\n  // `ServerSideFieldValidation` feature gate is disabled.\n  // - Warn: This will send a warning via the standard warning response\n  // header for each unknown field that is dropped from the object, and\n  // for each duplicate field that is encountered. The request will\n  // still succeed if there are no other errors, and will only persist\n  // the last of any duplicate fields. This is the default when the\n  // `ServerSideFieldValidation` feature gate is enabled.\n  // - Strict: This will fail the request with a BadRequest error if\n  // any unknown fields would be dropped from the object, or if any\n  // duplicate fields are present. The error returned from the server\n  // will contain all unknown and duplicate fields encountered.\n  // +optional\n  optional string fieldValidation = 3;\n}\n\n// Verbs masks the value so protobuf can generate\n//\n// +protobuf.nullable=true\n// +protobuf.options.(gogoproto.goproto_stringer)=false\nmessage Verbs {\n  // items, if empty, will result in an empty slice\n\n  repeated string items = 1;\n}\n\n// Event represents a single event to a watched resource.\n//\n// +protobuf=true\n// +k8s:deepcopy-gen=true\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\nmessage WatchEvent {\n  optional string type = 1;\n\n  // Object is:\n  //  * If Type is Added or Modified: the new state of the object.\n  //  * If Type is Deleted: the state of the object immediately before deletion.\n  //  * If Type is Error: *Status is recommended; other types may make sense\n  //    depending on context.\n  optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 2;\n}\n\n"
  },
  {
    "path": "third_party/kubernetes_proto/runtime/BUILD.bazel",
    "content": "load(\"@com_google_protobuf//bazel:cc_proto_library.bzl\", \"cc_proto_library\")\nload(\"@com_google_protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\nlicenses([\"notice\"])\n\nproto_library(\n    name = \"runtime_proto\",\n    srcs = [\"generated.proto\"],\n    visibility = [\"//visibility:public\"],\n)\n\ngo_proto_library(\n    name = \"runtime_go_proto\",\n    compilers = [\"@io_bazel_rules_go//proto:go_grpc\"],\n    importpath = \"github.com/googlecloudrobotics/core/third_party/kubernetes_proto/runtime\",\n    proto = \":runtime_proto\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    embed = [\":runtime_go_proto\"],\n    importpath = \"github.com/googlecloudrobotics/core/third_party/kubernetes_proto/runtime\",\n    visibility = [\"//visibility:public\"],\n)\n\ncc_proto_library(\n    name = \"runtime_cc_proto\",\n    visibility = [\"//visibility:public\"],\n    deps = [\":runtime_proto\"],\n)\n"
  },
  {
    "path": "third_party/kubernetes_proto/runtime/generated.proto",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n\n// This file was autogenerated by go-to-protobuf. Do not edit it manually!\n\nsyntax = \"proto2\";\n\npackage k8s.io.apimachinery.pkg.runtime;\n\n// Package-wide variables from generator \"generated\".\noption go_package = \"k8s.io/apimachinery/pkg/runtime\";\n\n// RawExtension is used to hold extensions in external versions.\n//\n// To use this, make a field which has RawExtension as its type in your external, versioned\n// struct, and Object in your internal struct. You also need to register your\n// various plugin types.\n//\n// // Internal package:\n//\n// \ttype MyAPIObject struct {\n// \t\truntime.TypeMeta `json:\",inline\"`\n// \t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n// \t}\n//\n// \ttype PluginA struct {\n// \t\tAOption string `json:\"aOption\"`\n// \t}\n//\n// // External package:\n//\n// \ttype MyAPIObject struct {\n// \t\truntime.TypeMeta `json:\",inline\"`\n// \t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n// \t}\n//\n// \ttype PluginA struct {\n// \t\tAOption string `json:\"aOption\"`\n// \t}\n//\n// // On the wire, the JSON will look something like this:\n//\n// \t{\n// \t\t\"kind\":\"MyAPIObject\",\n// \t\t\"apiVersion\":\"v1\",\n// \t\t\"myPlugin\": {\n// \t\t\t\"kind\":\"PluginA\",\n// \t\t\t\"aOption\":\"foo\",\n// \t\t},\n// \t}\n//\n// So what happens? Decode first uses json or yaml to unmarshal the serialized data into\n// your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked.\n// The next step is to copy (using pkg/conversion) into the internal struct. The runtime\n// package's DefaultScheme has conversion functions installed which will unpack the\n// JSON stored in RawExtension, turning it into the correct object type, and storing it\n// in the Object. (TODO: In the case where the object is of an unknown type, a\n// runtime.Unknown object will be created and stored.)\n//\n// +k8s:deepcopy-gen=true\n// +protobuf=true\n// +k8s:openapi-gen=true\nmessage RawExtension {\n  // Raw is the underlying serialization of this object.\n  //\n  // TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data.\n  optional bytes raw = 1;\n}\n\n// TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type,\n// like this:\n//\n// \ttype MyAwesomeAPIObject struct {\n// \t     runtime.TypeMeta    `json:\",inline\"`\n// \t     ... // other fields\n// \t}\n//\n// func (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind\n//\n// TypeMeta is provided here for convenience. You may use it directly from this package or define\n// your own with the same fields.\n//\n// +k8s:deepcopy-gen=false\n// +protobuf=true\n// +k8s:openapi-gen=true\nmessage TypeMeta {\n  // +optional\n  optional string apiVersion = 1;\n\n  // +optional\n  optional string kind = 2;\n}\n\n// Unknown allows api objects with unknown types to be passed-through. This can be used\n// to deal with the API objects from a plug-in. Unknown objects still have functioning\n// TypeMeta features-- kind, version, etc.\n// TODO: Make this object have easy access to field based accessors and settors for\n// metadata and field mutatation.\n//\n// +k8s:deepcopy-gen=true\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +protobuf=true\n// +k8s:openapi-gen=true\nmessage Unknown {\n  optional TypeMeta typeMeta = 1;\n\n  // Raw will hold the complete serialized object which couldn't be matched\n  // with a registered type. Most likely, nothing should be done with this\n  // except for passing it through the system.\n  optional bytes raw = 2;\n\n  // ContentEncoding is encoding used to encode 'Raw' data.\n  // Unspecified means no encoding.\n  optional string contentEncoding = 3;\n\n  // ContentType  is serialization method used to serialize 'Raw'.\n  // Unspecified means ContentTypeJSON.\n  optional string contentType = 4;\n}\n\n"
  },
  {
    "path": "third_party/kubernetes_proto/schema/BUILD.bazel",
    "content": "load(\"@com_google_protobuf//bazel:cc_proto_library.bzl\", \"cc_proto_library\")\nload(\"@com_google_protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\nload(\"@io_bazel_rules_go//proto:def.bzl\", \"go_proto_library\")\n\nlicenses([\"notice\"])\n\nproto_library(\n    name = \"schema_proto\",\n    srcs = [\"generated.proto\"],\n    visibility = [\"//visibility:public\"],\n)\n\ngo_proto_library(\n    name = \"schema_go_proto\",\n    compilers = [\"@io_bazel_rules_go//proto:go_grpc\"],\n    importpath = \"github.com/googlecloudrobotics/core/third_party/kubernetes_proto/schema\",\n    proto = \":schema_proto\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    embed = [\":schema_go_proto\"],\n    importpath = \"github.com/googlecloudrobotics/core/third_party/kubernetes_proto/schema\",\n    visibility = [\"//visibility:public\"],\n)\n\ncc_proto_library(\n    name = \"schema_cc_proto\",\n    visibility = [\"//visibility:public\"],\n    deps = [\":schema_proto\"],\n)\n"
  },
  {
    "path": "third_party/kubernetes_proto/schema/generated.proto",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n\n// This file was autogenerated by go-to-protobuf. Do not edit it manually!\n\nsyntax = \"proto2\";\n\npackage k8s.io.apimachinery.pkg.runtime.schema;\n\n// Package-wide variables from generator \"generated\".\noption go_package = \"k8s.io/apimachinery/pkg/runtime/schema\";\n\n"
  },
  {
    "path": "third_party/terraform.BUILD",
    "content": "exports_files([\"terraform\"])\n"
  }
]