[
  {
    "path": ".dockerignore",
    "content": "Dockerfile*\nJenkinsfile*\n**/.terraform\n.git/\n\n.idea/\n*.iml\n.gcloudignore\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\nmax_line_length = 120\ntrim_trailing_whitespace = true\n\n[*.py]\nindent_size = 4\n\n[{Makefile,makefile,**.mk}]\nindent_style = tab\n\n[*.sh]\nindent_style = space\nindent_size = 2\n\nshell_variant      = bash # like -ln=posix\nbinary_next_line   = true  # like -bn\nswitch_case_indent = true  # like -ci\nspace_redirects    = true  # like -sr\nkeep_padding       = false  # like -kp\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behaviour:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behaviour**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is.\nFor example: I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: Post a question about the project\ntitle: ''\nlabels: question\nassignees: ''\n---\n\n**Your question**\nA clear and concise question.\n\n**Additional context**\nAdd any other context about your question here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
    "content": "---\nname: Pull Request\nabout: A pull request\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n[pull_requests]: https://github.com/controlplaneio/kubesec/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc\n\n<!-- You can erase any parts of this template not applicable to your Pull Request. -->\n\n**All Submissions.**\n\n- [ ] Have you followed the guidelines in our [Contributing document](../../CONTRIBUTING.md)?\n- [ ] Have you checked to ensure there aren't other open [Pull Requests][pull_requests] for the same update/change?\n\n**Code Submissions.**\n\n- [ ] Does your submission pass linting, tests, and security analysis?\n\n**Changes to Core Features.**\n\n- [ ] Have you added an explanation of what your changes do and why you'd like us to include them?\n- [ ] Have you written new tests for your core changes, as applicable?\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: Lint and Build\non:\n  push:\n    tags-ignore:\n      - '*'\n    branches:\n    - '*'\n  pull_request:\n    branches: ['main', 'master']\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3\n\n      - name: Run golangci-lint\n        uses: reviewdog/action-golangci-lint@f9bba13753278f6a73b27a56a3ffb1bfda90ed71 # v2\n        with:\n          go_version: \"1.25.4\"\n          fail_level: \"none\"\n\n  build:\n    runs-on: ubuntu-latest\n    needs: lint\n    steps:\n      - name: Checkout source code\n        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3\n\n      - name: Setup Go\n        uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # v3\n        with:\n          go-version: '1.25.4'\n\n      - name: Install dependencies\n        run: go get ./...\n\n      - name: Test\n        run: go test -v ./... --race\n\n      - name: E2E Test\n        env:\n          KIND_E2E_TESTS: yes\n        run: go test -timeout 20m -v ./e2e/...\n\n      - name: Build\n        run: go build -v ./...\n\n      - name: Build Container\n        run: go build -v ./...\n\n      - name: Build an image from Dockerfile\n        run: |\n          docker build -t controlplane/netassert:${{ github.sha }} .\n\n      - name: Run Trivy vulnerability scanner\n        uses: aquasecurity/trivy-action@master\n        with:\n          image-ref: 'controlplane/netassert:${{ github.sha }}'\n          format: 'table'\n          ignore-unfixed: true\n          exit-code: '1'\n          vuln-type: 'os,library'\n          severity: 'CRITICAL,HIGH,MEDIUM'\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: release\n\non:\n  push:\n    tags:\n      - \"v[0-9]+.[0-9]+.[0-9]+\"\n      - \"v[0-9]+.[0-9]+.[0-9]+-testing[0-9]+\"\n\npermissions:\n  contents: write\n  packages: write\n  id-token: write\n  attestations: write\n\nenv:\n  GH_REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n  RELEASE_VERSION: ${{ github.ref_name }}\n  SCANNER_IMG_VERSION: v1.0.11\n  SNIFFER_IMG_VERSION: v1.1.9\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      - name: Set up Go\n        uses: actions/setup-go@be3c94b385c4f180051c996d336f57a34c397495 # v3\n        with:\n          go-version: '1.25.4'\n\n      - uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # v0.20.6\n\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@5fdedb94abba051217030cc86d4523cf3f02243d # v4\n        with:\n          distribution: goreleaser\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  docker:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      # - name: Extract metadata (tags, labels) for Docker\n      #   id: meta\n      #   uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7\n      #   with:\n      #     images: ${{ env.GH_REGISTRY }}/${{ env.IMAGE_NAME }}\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3\n\n      - name: Install cosign\n        uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3\n\n      - name: Log in to the GitHub Container registry\n        uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1\n        with:\n          registry: ${{ env.GH_REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push\n        id: buildpush\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6\n        with:\n          platforms: linux/amd64,linux/arm64\n          sbom: true\n          provenance: mode=max\n          push: true\n          tags: |\n            docker.io/controlplane/netassert:${{ env.RELEASE_VERSION }}\n            docker.io/controlplane/netassert:latest\n            ${{ env.GH_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.RELEASE_VERSION }}\n            ${{ env.GH_REGISTRY }}/${{ env.IMAGE_NAME }}:latest\n          build-args: |\n            VERSION=${{ env.RELEASE_VERSION }}\n            SCANNER_IMG_VERSION=${{ env.SCANNER_IMG_VERSION }}\n            SNIFFER_IMG_VERSION=${{ env.SNIFFER_IMG_VERSION }}\n\n      - name: Sign artifact\n        run: |\n          cosign sign --yes \\\n            \"${{ env.GH_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.buildpush.outputs.digest }}\"\n          cosign sign --yes \\\n            \"docker.io/controlplane/netassert@${{ steps.buildpush.outputs.digest }}\"\n\n  helm:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      - name: Set up Helm\n        uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4\n\n      - name: Setup yq\n        uses: mikefarah/yq@065b200af9851db0d5132f50bc10b1406ea5c0a8 # v4\n\n      - name: Log in to GitHub Container Registry\n        run: |\n          echo \"${{ secrets.GITHUB_TOKEN }}\" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin\n\n      - name: Prepare and package Helm chart\n        run: |\n          CLEAN_VERSION=$(echo \"$RELEASE_VERSION\" | sed 's/^v//')\n          echo \"Using chart version and appVersion: $CLEAN_VERSION\"\n          yq -i \".image.tag = \\\"${RELEASE_VERSION}\\\"\" ./helm/values.yaml\n          yq -i \".version = \\\"${CLEAN_VERSION}\\\"\" ./helm/Chart.yaml\n          yq -i \".appVersion = \\\"${CLEAN_VERSION}\\\"\" ./helm/Chart.yaml\n          helm package ./helm -d .\n\n      - name: Push Helm chart to GHCR\n        run: |\n          CLEAN_VERSION=$(echo \"$RELEASE_VERSION\" | sed 's/^v//')\n          helm push \"./netassert-${CLEAN_VERSION}.tgz\" oci://ghcr.io/${{ github.repository_owner }}/charts\n"
  },
  {
    "path": ".gitignore",
    "content": "# Secrets #\n###########\n*.pem\n*.key\n*_rsa\n\n# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n*.pyc\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n\n# Logs and databases #\n######################\n*.log\n*.sqlite\npip-log.txt\n\n# OS generated files #\n######################\n.DS_Store?\nehthumbs.db\nIcon?\nThumbs.db\n\n# IDE generated files #\n#######################\n.idea/\n*.iml\natlassian-ide-plugin.xml\n\n# Test Files #\n##############\ntest/log\n.coverage\n.tox\nnosetests.xml\n\n# Package Managed Files #\n#########################\nbower_components/\nvendor/\ncomposer.lock\nnode_modules/\n.npm/\nvenv/\n.venv/\n.venv2/\n.venv3/\n\n# temporary files #\n###################\n*.*swp\nnohup.out\n*.tmp\n\n# Virtual machines #\n####################\n.vagrant/\n\n# Pythonics #\n#############\n*.py[cod]\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Complexity\noutput/*.html\noutput/*/index.html\n\n# Sphinx\ndocs/_build\n.scratch.md\n\nconf/.config/keybase/\n\n# Pipenv\n\nPipfile*\n# backup files\n*.backup\n*.notworking\n\ninternal/types-not-used/\ndemo.yaml\n.idea\ncmd/netassert/cli/netassert\ncmd/netassert/cli/results.tap\ninternal/logger/*.old\ncmd/netassert/cli/cli\n\n# Terraform\n.terraform\n*.tfstate\n*.tfstate.*\ncrash.log\ncrash.*.log\n*.tfvars\n*.tfvars.json\noverride.tf\noverride.tf.json\n*_override.tf\n*_override.tf.json\n.terraformrc\nterraform.rc\n*.lock.hcl*\n\n# Kubeconfig\n*.kubeconfig\n\n# CLI\n/cmd/netassert/cli/*.sh\nabc\nnetassert-*-*-kubeconfig\nbin\nresults.tap\n"
  },
  {
    "path": ".goreleaser.yaml",
    "content": "builds:\n- id: netassert\n  env:\n  - CGO_ENABLED=0\n  ldflags:\n  - -s\n  - -w\n  - -X main.version={{.Tag}}\n  - -X main.gitHash={{.FullCommit}}\n  - -X main.buildDate={{.Date}}\n  goos:\n  - linux\n  - darwin\n  - windows\n  goarch:\n  - amd64\n  - arm\n  - arm64\n  goarm:\n  - 6\n  - 7\n  main: ./cmd/netassert/cli/\n  binary: netassert\narchives:\n- id: netassert\n  name_template: \"{{ .ProjectName }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}\"\n  format: tar.gz\n  format_overrides:\n  - goos: windows\n    format: zip\n  files:\n  - LICENSE\n  wrap_in_directory: false\nchecksum:\n  algorithm: sha256\n  name_template: 'checksums-sha256.txt'\nchangelog:\n  sort: asc\nsboms:\n  - id: archive\n    artifacts: archive\n  - id: source\n    artifacts: source\n"
  },
  {
    "path": ".hadolint.yaml",
    "content": "---\nignored:\n  - DL3018 # Pin versions in apk add.\n  - DL3022 # COPY --from alias\n"
  },
  {
    "path": ".yamllint.yaml",
    "content": "---\nextends: default\n\nignore: |\n  test/bin/\n  test/asset/\nrules:\n  comments:\n    min-spaces-from-content: 1\n  line-length:\n    max: 120\n  truthy:\n    check-keys: false\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## Table of Contents\n\n- [2.0.3](#203)\n- [2.0.2](#202)\n- [2.0.1](#201)\n- [2.0.0](#200)\n- [0.1.0](#010)\n\n---\n\n## `2.0.3`\n\n- test with latest version of Kubernetes and update to Go 1.21\n- update e2e tests with latest version of EKS and GKE and Calico CNI\n\n## `2.0.2`\n\n- integrate e2e tests with network policies\n- fix a bug in udp testing\n\n## `2.0.1`\n\n- fix release naming\n\n## `2.0.0`\n\n- complete rewrite of the tool in Go, with unit and integration tests\n- leverages the ephemeral container support in Kubernetes > v1.25\n- test case(s) are written in YAML\n- support for Pods, StatefulSets, DaemonSets and Deployments which are directly referred through their names in the test suites\n- artifacts are available for download\n\n## `0.1.0`\n\n- initial release\n- no artifacts available\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\neducation, socio-economic status, nationality, personal appearance, race,\nreligion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior\nmay be reported by contacting Andrew Martin andy(at)control-plane.io.\nAll complaints will be reviewed and investigated and will result in a response that is deemed\nnecessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of\nan incident. Further details of specific enforcement policies may be\nposted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to NetAssert\n\n:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:\n\n`NetAssert` is Apache 2.0 licensed and accepts contributions via GitHub pull requests.\n\nThe following is a set of guidelines for contributing to `NetAssert`. We generally have stricter rules as it's a security\ntool but don't let that discourage you from creating your PR, it can be incrementally fixed to fit the rules. Also feel\nfree to propose changes to this document in a pull request.\n\n## Table Of Contents\n\n- [Contributing to NetAssert](#contributing-to-netassert)\n  - [Table Of Contents](#table-of-contents)\n  - [Code of Conduct](#code-of-conduct)\n  - [I Don't Want To Read This Whole Thing I Just Have a Question!!!](#i-dont-want-to-read-this-whole-thing-i-just-have-a-question)\n  - [What Should I Know Before I Get Started?](#what-should-i-know-before-i-get-started)\n  - [How Can I Contribute?](#how-can-i-contribute)\n    - [Reporting Bugs](#reporting-bugs)\n      - [Before Submitting a Bug Report](#before-submitting-a-bug-report)\n      - [How Do I Submit a (Good) Bug Report?](#how-do-i-submit-a-good-bug-report)\n    - [Suggesting Enhancements](#suggesting-enhancements)\n      - [Before Submitting an Enhancement Suggestion](#before-submitting-an-enhancement-suggestion)\n      - [How Do I Submit A (Good) Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)\n    - [Your First Code Contribution](#your-first-code-contribution)\n      - [Development](#development)\n    - [Pull Requests](#pull-requests)\n  - [Style Guides](#style-guides)\n    - [Git Commit Messages](#git-commit-messages)\n    - [General Style Guide](#general-style-guide)\n    - [GoLang Style Guide](#golang-style-guide)\n    - [Documentation Style Guide](#documentation-style-guide)\n\n---\n\n## Code of Conduct\n\nThis project and everyone participating are governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you\nare expected to uphold this code. Please report unacceptable behaviour to [andy@control-plane.io](mailto:andy@control-plane.io).\n\n## I Don't Want To Read This Whole Thing I Just Have a Question!!!\n\nWe have an official message board with a detailed FAQ and where the community chimes in with helpful advice if you have questions.\n\nWe also have an issue template for questions [here](https://github.com/controlplaneio/netassert/issues/new).\n\n## What Should I Know Before I Get Started?\n\nNetassert has three components:\n\n- [NetAssert](https://github.com/controlplaneio/netassert): This is responsible for orchestrating the tests and is also known as `netassert-engine`\n- [NetAssertv2-packet-sniffer](https://github.com/controlplaneio/netassertv2-packet-sniffer): This is the sniffer component that is utilised during a UDP test and is injected to the destination/target Pod as an ephemeral container\n- [NetAssertv2-l4-client](https://github.com/controlplaneio/netassertv2-l4-client): This is the scanner component that is injected as the scanner ephemeral container onto the source Pod and is utilised during both TCP and UDP tests\n\n## How Can I Contribute?\n\n### Reporting Bugs\n\nThis section guides you through submitting a bug report for `NetAssert`. Following these guidelines helps maintainers and the\ncommunity understand your report, reproduce the behaviour, and find related reports.\n\nBefore creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you\ndon't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report).\nFill out the issue template for bugs, the information it asks for helps us resolve issues faster.\n\n> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue\n> and include a link to the original issue in the body of your new one.\n\n#### Before Submitting a Bug Report\n\n- **Perform a [cursory search](https://github.com/search?q=+is:issue+user:controlplaneio)** to see if the problem has already\n  been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new\n  one\n\n#### How Do I Submit a (Good) Bug Report?\n\nBugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on that repository and\nprovide the following information by filling in the issue template [here](https://github.com/controlplaneio/netassert/issues/new).\n\nExplain the problem and include additional details to help maintainers reproduce the problem:\n\n- **Use a clear and descriptive title** for the issue to identify the problem\n- **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining\n- **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable\n  snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines)\n- **Describe the behaviour you observed after following the steps** and point out what exactly is the problem with that behaviour\n- **Explain which behaviour you expected to see instead and why.**\n\nProvide more context by answering these questions:\n\n- **Did the problem start happening recently** (e.g. after updating to a new version of netassert) or was this always a problem?\n- If the problem started happening recently, **can you reproduce the problem in an older version of netassert?** What's the\n  most recent version in which the problem doesn't happen? You can download older versions of netassert from\n  [the releases page](https://github.com/controlplaneio/netassert/releases)\n- **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions\n  it normally happens\n- If the problem is related to scanning files, **does the problem happen for all files and projects or only some?** Is there\n  anything else special about the files you are using? Please include them in your report, censor any sensitive information\n  but ensure the issue still exists with the censored file\n\n### Suggesting Enhancements\n\nThis section guides you through submitting an enhancement suggestion for netassert, including completely new features and minor\nimprovements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion\nand find related suggestions.\n\nBefore creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might\nfind out that you don't need to create one. When you are creating an enhancement suggestion, please\n[include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in the template feature request\ntemplate, including the steps that you imagine you would take if the feature you're requesting existed.\n\n#### Before Submitting an Enhancement Suggestion\n\n- **Perform a [cursory search](https://github.com/search?q=+is:issue+user:controlplaneio)** to see if the enhancement has\n  already been suggested. If it has, add a comment to the existing issue instead of opening a new one\n\n#### How Do I Submit A (Good) Enhancement Suggestion?\n\nEnhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on this\nrepository and provide the following information:\n\n- **Use a clear and descriptive title** for the issue to identify the suggestion\n- **Provide a step-by-step description of the suggested enhancement** in as many details as possible\n- **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples,\n  as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines)\n- **Describe the current behaviour** and **explain which behaviour you expected to see instead** and why\n- **Explain why this enhancement would be useful** to most netassert users and isn't something that can or should be implemented\n  as a separate community project\n- **List some other tools where this enhancement exists.**\n- **Specify which version of netassert you're using.** You can get the exact version by running `netassert version` in your terminal\n- **Specify the name and version of the OS you're using.**\n\n### Your First Code Contribution\n\nUnsure where to begin contributing to `netassert`? You can start by looking through these `Good First Issue` and `Help Wanted`\nissues:\n\n- [Good First Issue issues][good_first_issue] - issues which should only require a few lines of code, and a test or two\n- [Help wanted issues][help_wanted] - issues which should be a bit more involved than `Good First Issue` issues\n\nBoth issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for\nimpact a given change will have.\n\n#### Development\n\nDownload the latest version of [just](https://github.com/casey/just/releases). To build the project you can use `just build`. The resulting binary will be in `cmd/netassert/cli/netassert`. To run `unit` tests you can use `just test`. There is a seperate `README.md` in the `e2e` folder that lives in the root of this project that details `end-to-end` testing.\n\n### Pull Requests\n\nThe process described here has several goals:\n\n- Maintain the quality of `netassert`\n- Fix problems that are important to users\n- Engage the community in working toward the best possible netassert\n- Enable a sustainable system for netassert's maintainers to review contributions\n\nPlease follow these steps to have your contribution considered by the maintainers:\n\n<!-- markdownlint-disable no-inline-html -->\n\n1. Follow all instructions in the template\n2. Follow the [style guides](#style-guides)\n3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/)\n   are passing\n   <details>\n    <summary>What if the status checks are failing?</summary>\n    If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on\n    the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for\n    you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our\n    status check suite.\n   </details>\n\n<!-- markdownlint-enable no-inline-html -->\n\nWhile the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to\ncomplete additional tests, or other changes before your pull request can be ultimately accepted.\n\n## Style Guides\n\n### Git Commit Messages\n\n- It's strongly preferred you [GPG Verify][commit_signing] your commits if you can\n- Follow [Conventional Commits](https://www.conventionalcommits.org)\n- Use the present tense (\"add feature\" not \"added feature\")\n- Use the imperative mood (\"move cursor to...\" not \"moves cursor to...\")\n- Limit the first line to 72 characters or less\n- Reference issues and pull requests liberally after the first line\n\n### General Style Guide\n\nLook at installing an `.editorconfig` plugin or configure your editor to match the `.editorconfig` file in the root of the\nrepository.\n\n### GoLang Style Guide\n\nAll Go code is linted with [golangci-lint](https://golangci-lint.run/).\n\nFor formatting rely on `gofmt` to handle styling.\n\n### Documentation Style Guide\n\nAll markdown code is linted with [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli).\n\n[good_first_issue]:https://github.com/controlplaneio/netassert/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22+sort%3Acomments-desc\n[help_wanted]: https://github.com/controlplaneio/netassert/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22\n\n[commit_signing]: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/managing-commit-signature-verification\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.25-alpine AS builder\n\nARG VERSION\n\nCOPY . /build\nWORKDIR /build\n\nRUN go mod download && \\\n    CGO_ENABLED=0 GO111MODULE=on go build -ldflags=\"-X 'main.appName=NetAssert' -X 'main.version=${VERSION}' -X 'main.scannerImgVersion=${SCANNER_IMG_VERSION}' -X 'main.snifferImgVersion=${SNIFFER_IMG_VERSION}'\" -v -o /netassertv2 cmd/netassert/cli/*.go && \\\n    ls -ltr /netassertv2\n\nFROM gcr.io/distroless/base:nonroot\nCOPY --from=builder /netassertv2 /usr/bin/netassertv2\n\nENTRYPOINT [ \"/usr/bin/netassertv2\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 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 2017 control-plane.io\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": "README.md",
    "content": "# Netassert\n\n[![Testing Workflow][testing_workflow_badge]][testing_workflow_badge]\n[![Release Workflow][release_workflow_badge]][release_workflow_badge]\n\n`NetAssert` is a command line tool that enables you to check the network connectivity between Kubernetes objects such as Pods, Deployments, DaemonSets, and StatefulSets, as well as test their connectivity to remote hosts or IP addresses. `NetAssert` v2 is a rewrite of original `NetAssert` tool in Go that utilises the ephemeral container support in Kubernetes to verify network connectivity. `NetAssert` test(s) are defined in YAML format. `NetAssert` **currently supports TCP and UDP protocols**:\n\n- To perform a TCP test, only a [`scanner`](https://github.com/controlplaneio/netassertv2-l4-client) container is used. This container requires no privileges nor any Linux capabilities.\n\n- To run a UDP test, a [`sniffer`](https://github.com/controlplaneio/netassertv2-packet-sniffer) ephemeral container is injected into the target Pod which requires `cap_raw` capabilities to read data from the network interface. During UDP testing, `NetAssert` runs both container `scanner` and `sniffer` container images which are injected as `ephemeral` containers into running Pods.\n\nThe [`sniffer`](https://github.com/controlplaneio/netassertv2-packet-sniffer) and [`scanner`](https://github.com/controlplaneio/netassertv2-l4-client)  container images can be downloaded from:\n\n- `docker.io/controlplane/netassertv2-l4-client:latest`\n  - Used for both TCP and UDP testing and acts as a Layer 4 (TCP/UDP) client\n  - Requires no privileges nor any Linux capabilities.\n- `docker.io/controlplane/netassertv2-packet-sniffer:latest`\n  - Used for UDP testing only, injected at the destination to capture packet and search for specific string in the payload\n  - requires `cap_raw` capabilities to read data from the network interface\n\n`NetAssert` utilises the above containers during test and configures them using *environment variables*. The list of environment variables that are used can be found [here](https://github.com/controlplaneio/netassertv2-packet-sniffer) and [here](https://github.com/controlplaneio/netassertv2-l4-client). It is possible to override the `sniffer` and `scanner` images from command line during a run, so one can also bring their own container image(s) as long as they support the same environment variables.\n\n<img src=\"./img/demo.gif\">\n\n## Installation\n\n- Please download the latest stable version of `NetAssert` from [releases](https://github.com/controlplaneio/netassert/releases) page. The binary is available for Linux, MacOS and Windows platforms.\n\n- If you are on Unix/Linux, you can also use the [download.sh](./download.sh) script to download the latest version of `NetAssert` into the current path:\n\n```bash\ncurl -sL https://raw.githubusercontent.com/controlplaneio/netassert/master/download.sh | bash\n```\n\n## Test specification\n\n`NetAssert` v2 tests are written in YAML format. Each test is a YAML document which supports the following mappings:\n\n- A YAML document is a list of `NetAssert` test. Each test has the following keys:\n  - **name**: a scalar representing the name of the connection\n  - **type**: a scalar representing the type of connection, only \"k8s\" is supported at this time\n  - **protocol**: a scalar representing the protocol used for the connection, which must be \"tcp\" or \"udp\"\n  - **targetPort**: an integer scalar representing the target port used by the connection\n  - **timeoutSeconds**: an integer scalar representing the timeout for the connection in seconds\n  - **attempts**: an integer scalar representing the number of connection attempts for the test\n  - **exitCode**: an integer scalar representing the expected exit code from the ephemeral/debug container(s)\n  - **src**: a mapping representing the source Kubernetes resource, which has the following keys:\n    - **k8sResource**: a mapping representing a Kubernetes resource with the following keys:\n      - **kind**: a scalar representing the kind of the Kubernetes resource, which can be `deployment`, `statefulset`, `daemonset` or `pod`\n      - **name**: a scalar representing the name of the Kubernetes resource\n      - **namespace**: a scalar representing the namespace of the Kubernetes resource\n  - **dst**: a mapping representing the destination Kubernetes resource or host, **which can have one of the the following keys** i.e both `k8sResource` and `host` **are not supported at the same time** :\n    - **k8sResource**: a mapping representing a Kubernetes resource with the following keys:\n      - **kind**: a scalar representing the kind of the Kubernetes resource, which can be `deployment`, `statefulset`, `daemonset` or `pod`\n      - **name**: a scalar representing the name of the Kubernetes resource\n      - **namespace**: a scalar representing the namespace of the Kubernetes resource. (Note: Only allowed when protocol is \"tcp\")\n    - **host**: a mapping representing a host/node with the following key:\n      - **name**: a scalar representing the name or IP address of the host/node. (Note: Only allowed when protocol is \"tcp\" or \"udp\", but not both at the same time)\n\n<details><summary>This is an example of a test that can be consumed by `NetAssert` utility</summary>\n\n```yaml\n---\n- name: busybox-deploy-to-echoserver-deploy\n  type: k8s\n  protocol: tcp\n  targetPort: 8080\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource:\n      kind: deployment\n      name: echoserver\n      namespace: echoserver\n#######\n#######\n- name: busybox-deploy-to-core-dns\n  type: k8s\n  protocol: udp\n  targetPort: 53\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource:\n      kind: deployment\n      name: coredns\n      namespace: kube-system\n######\n######\n- name: busybox-deploy-to-web-statefulset\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: # this is type endpoint\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource: ## this is type endpoint\n      kind: statefulset\n      name: web\n      namespace: web\n###\n###\n- name: fluentd-daemonset-to-web-statefulset\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: # this is type endpoint\n      kind: daemonset\n      name: fluentd\n      namespace: fluentd\n  dst:\n    k8sResource: ## this is type endpoint\n      kind: statefulset\n      name: web\n      namespace: web\n###\n####\n- name: busybox-deploy-to-control-plane-dot-io\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: # type endpoint\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    host: # type host or node or machine\n      name: control-plane.io\n###\n###\n- name: test-from-pod1-to-pod2\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: ##\n      kind: pod\n      name: pod1\n      namespace: pod1\n  dst:\n    k8sResource:\n      kind: pod\n      name: pod2\n      namespace: pod2\n###\n###\n- name: busybox-deploy-to-fake-host\n  type: k8s\n  protocol: tcp\n  targetPort: 333\n  timeoutSeconds: 67\n  attempts: 3\n  exitCode: 1\n  src:\n    k8sResource: # type endpoint\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    host: # type host or node or machine\n      name: 0.0.0.0\n...\n```\n\n</details>\n\n## Components\n\n`NetAssert` has three main components:\n\n- [NetAssert](https://github.com/controlplaneio/netassert): This is responsible for orchestrating the tests and is also known as `Netassert-Engine` or simply the `Engine`\n- [NetAssertv2-packet-sniffer](https://github.com/controlplaneio/netassertv2-packet-sniffer): This is the sniffer component that is utilised during a UDP test and is injected to the destination/target Pod as an ephemeral container\n- [NetAssertv2-l4-client](https://github.com/controlplaneio/netassertv2-l4-client): This is the scanner component that is injected as the scanner ephemeral container onto the source Pod and is utilised during both TCP and UDP tests\n\n## Detailed steps/flow of tests\n\nAll the tests are read from an YAML file or a directory (step **1**) and the results are written following the [TAP format](https://testanything.org/) (step **5** for UDP and step **4** for TCP). The tests are performed in two different manners depending on whether a TCP or UDP connection is used\n\n### UDP test\n\n<img src=\"./img/udp-test.svg\">\n\n- Validate the test spec and ensure that the `src` and `dst` fields are correct: for udp tests both of them must be of type `k8sResource`\n- Find a running Pod called `dstPod` in the object defined by the `dst.k8sResource` field. Ensure that the Pod is in running state and has an IP address allocated by the CNI\n- Find a running Pod called `srcPod` in the object defined by the `src.k8sResource` field. Ensure that the Pod is in running state and has an IP address allocated by the CNI\n- Generate a random UUID, which will be used by both ephemeral containers\n- Inject the `netassert-l4-client` as an ephemeral container in the `srcPod` (step **2**) and set the port and protocol according to the test specifications. Provide also the target host equal to the previously found dstPod IP address, and the random UUID that was generated in the previous step as the message to be sent over the udp connection. At the same time, inject the `netassertv2-packet-sniffer` (step **3**) as an ephemeral container in the `dstPod` using the protocol, search string, number of matches and timeout defined in the test specifications. The search_string environment variable is equal to the UUID that was generated in the previous step which is expected to be found in the data sent by the scanner when the connections are successful.\n- Poll that status of the ephemeral containers (step **4**)\n- Ensure that the `netassertv2-packet-sniffer` ephemeral sniffer container’s exit status matches the one defined in the test specification\n- Ensure that the `netassert-l4-client`, exits with exit status of zero. This should always be the case as UDP is not a connection oriented protocol.\n\n### TCP test\n\n<img src=\"./img/tcp-test.svg\">\n\n- Validate the test spec and ensure that the `src` field is of type `k8sResource`\n- Find a running Pod called `srcPod` in the object defined by the `src.k8sResource` field. Ensure that the Pod is in running state and has an IPAddress\n- Check if `dst` has `k8sResource` defined as a child object. If so then find a running Pod defined by the `dst.K8sResource`\n- Inject the `netassert-l4-client` as an ephemeral container in the `srcPod` (step **2**). Configure the `netassert-l4-client` similarly to the udp case. If the `dst` field is set to `host` then use the host `name` field as the scanner target host\n- Poll that status of the ephemeral containers (step **3**)\n- Ensure that the exit code of that container matches the `exitCode` field defined in the test specification\n\n## Development\n\n- You will need Go version 1.25.x or higher. Download the latest version of [just](https://github.com/casey/just/releases). To build the project you can use `just build`. The resulting binary will be in `cmd/netassert/cli/netassert`. To run `unit` tests you can use `just test`. There is a separate [README.md](./e2e/README.md) that details `end-to-end` testing.\n\n## Quick testing\n\n### Spinning up the environment\n\n- Make sure you have installed [`kind`](https://kind.sigs.k8s.io/) and its prerequisites\n- Make sure you have also installed [`just`](https://github.com/casey/just/releases)\n- Download the `NetAssert` binary from the [release](https://github.com/controlplaneio/netassert/releases) page:\n\n```bash\n❯ VERSION=\"v2.1.3\" # change it to the version you want to install\n❯ OS_DISTRO=linux_amd64 # change it to your OS_DISTRO (for reference check the NetAssert release page)\n❯ curl -L -o netassert.tar.gz https://github.com/controlplaneio/netassert/releases/download/${VERSION}/netassert_${VERSION}_${OS_ARCH}.tar.gz\n\n❯ tar -xzf netassert.tar.gz -C bin/netassert\n```\n\n- Alternatively, you can build `NetAssert` from source:\n```bash\n❯ just build\n```\n\n- You will also need a working kubernetes cluster with ephemeral/debug container support and a CNI that supports Network Policies, you can spin one quickly using the `justfile` included in the repo:\n\n```bash\n❯ just kind-down ; just kind-up\n❯ just calico-apply\n```\n\n- wait for all the nodes to become ready:\n```bash\n❯ kubectl get nodes -w\n```\n\n### Running the sample tests\n\n- In order to use the sample tests, you need to create network policies and kubernetes resources:\n\n```bash\n❯ just k8s-apply\n  kubectl apply -f ./e2e/manifests/workload.yaml\n  namespace/fluentd created\n  daemonset.apps/fluentd created\n  namespace/echoserver created\n  namespace/busybox created\n  deployment.apps/echoserver created\n  deployment.apps/busybox created\n  namespace/pod1 created\n  namespace/pod2 created\n  pod/pod2 created\n  pod/pod1 created\n  namespace/web created\n  statefulset.apps/web created\n```\n\n```bash\n❯ just netpol-apply\n  kubectl apply -f ./e2e/manifests/networkpolicies.yaml\n  networkpolicy.networking.k8s.io/web created\n```\n\n- Wait for the workload to become ready (note that the workload pods are the ones created after running `just k8s-apply` in a previous step):\n```bash\n❯ kubectl get pods -A\n  busybox              busybox-6c85d76fdc-r8gtp                            1/1     Running   0          76s\n  echoserver           echoserver-64bd7c5dc6-ldwh9                         1/1     Running   0          76s\n  fluentd              fluentd-5pp9c                                       1/1     Running   0          76s\n  fluentd              fluentd-8vvp9                                       1/1     Running   0          76s\n  fluentd              fluentd-9jblb                                       1/1     Running   0          76s\n  fluentd              fluentd-jnlql                                       1/1     Running   0          76s\n  kube-system          calico-kube-controllers-565c89d6df-8mwk9            1/1     Running   0          117s\n  kube-system          calico-node-2sqhw                                   1/1     Running   0          117s\n  kube-system          calico-node-4sxpn                                   1/1     Running   0          117s\n  kube-system          calico-node-5gtg7                                   1/1     Running   0          117s\n  kube-system          calico-node-kxjq8                                   1/1     Running   0          117s\n  kube-system          coredns-7d764666f9-74xgb                            1/1     Running   0          2m29s\n  kube-system          coredns-7d764666f9-jvnr4                            1/1     Running   0          2m29s\n  kube-system          etcd-packet-test-control-plane                      1/1     Running   0          2m35s\n  kube-system          kube-apiserver-packet-test-control-plane            1/1     Running   0          2m35s\n  kube-system          kube-controller-manager-packet-test-control-plane   1/1     Running   0          2m35s\n  kube-system          kube-proxy-4xjp2                                    1/1     Running   0          2m27s\n  kube-system          kube-proxy-b28pw                                    1/1     Running   0          2m29s\n  kube-system          kube-proxy-p9smj                                    1/1     Running   0          2m27s\n  kube-system          kube-proxy-xb2wq                                    1/1     Running   0          2m27s\n  kube-system          kube-scheduler-packet-test-control-plane            1/1     Running   0          2m35s\n  local-path-storage   local-path-provisioner-67b8995b4b-jf8lc             1/1     Running   0          2m29s\n  pod1                 pod1                                                1/1     Running   0          75s\n  pod2                 pod2                                                1/1     Running   0          76s\n  web                  web-0                                               1/1     Running   0          75s\n  web                  web-1                                               1/1     Running   0          31s\n```\n- Run the netassert binary pointing it to the test cases:\n\n```bash\n❯ bin/netassert run --input-file ./e2e/manifests/test-cases.yaml\n\n❯ cat results.tap \nTAP version 14\n1..9\nok 1 - busybox-deploy-to-echoserver-deploy\nok 2 - busybox-deploy-to-echoserver-deploy-2\nok 3 - fluentd-deamonset-to-echoserver-deploy\nok 4 - busybox-deploy-to-web-statefulset\nok 5 - web-statefulset-to-busybox-deploy\nok 6 - fluentd-daemonset-to-web-statefulset\nok 7 - busybox-deploy-to-control-plane-dot-io\nok 8 - test-from-pod1-to-pod2\nok 9 - busybox-deploy-to-fake-host\n```\n\n- To see the results when a check fails, run:\n\n```bash\n❯ just netpol-rm-apply\n  kubectl delete -f ./e2e/manifests/networkpolicies.yaml\n  networkpolicy.networking.k8s.io \"web\" deleted\n\n❯ bin/netassert run --input-file ./e2e/manifests/test-cases.yaml\n\n❯ cat results.tap\nTAP version 14\n1..9\nok 1 - busybox-deploy-to-echoserver-deploy\nok 2 - busybox-deploy-to-echoserver-deploy-2\nok 3 - fluentd-deamonset-to-echoserver-deploy\nok 4 - busybox-deploy-to-web-statefulset\nnot ok 5 - web-statefulset-to-busybox-deploy\n  ---\n  reason: ephemeral container netassertv2-client-aihlpxcys exit code for test web-statefulset-to-busybox-deploy\n  is 0 instead of 1\n  ...\nok 6 - fluentd-daemonset-to-web-statefulset\nok 7 - busybox-deploy-to-control-plane-dot-io\nok 8 - test-from-pod1-to-pod2\nok 9 - busybox-deploy-to-fake-host\n```\n\n## Compatibility\n\nNetAssert is architected for compatibility with Kubernetes versions that offer support for ephemeral containers. We have thoroughly tested NetAssert with Kubernetes versions 1.25 to 1.35, confirming compatibility and performance stability.\n\nFor broader validation, our team has also executed comprehensive [end-to-end tests](./e2e/README.md) against various Kubernetes distributions and CNIs which is detailed below:\n\n| Kubernetes Distribution | Supported Version | Container Network Interface (CNI) |\n|-------------------------|-------------------|------------------------------------\n| Amazon EKS              | 1.34 and higher   | AWS VPC CNI                       |\n| Amazon EKS              | 1.34 and higher   | Calico (Version 3.26 or later)    |\n| Google GKE              | 1.33 and higher   | Google Cloud Platform VPC CNI     |\n| Google GKE              | 1.33 and higher   | Google Cloud Dataplane V2         |\n\n## Checking for ephemeral container support\n\nYou can check for ephemeral container support using the following command:\n\n```bash\n❯ netassert ping\n2023-03-27T11:25:28.421+0100 [INFO]  [NetAssert-v2.0.0]: ✅ Successfully pinged /healthz endpoint of the Kubernetes server\n2023-03-27T11:25:28.425+0100 [INFO]  [NetAssert-v2.0.0]: ✅ Ephemeral containers are supported by the Kubernetes server\n```\n\n## Increasing logging verbosity\n\nYou can increase the logging level to `debug` by passing `--log-level` argument:\n\n```bash\n❯ netassert run --input-file ./e2e/manifests/test-cases.yaml --log-level=debug\n```\n\n## RBAC Configuration\n\nThis tool can be run according to the Principle of Least Privilege (PoLP) by properly configuring the RBAC.\n\nThe list of required permissions can be found in the `netassert` ClusterRole `rbac/cluster-role.yaml`, which could be redefined as a Role for namespacing reasons if needed. This role can then be bound to a \"principal\" either through a RoleBinding or a ClusterRoleBinding, depending on whether the scope of the role is supposed to be namespaced or not. The ClusterRoleBinding `rbac/cluster-rolebinding.yaml` is an example where the user `netassert-user` is assigned the role `netassert` using a cluster-wide binding called `netassert`\n\n## Limitations\n\n- When performing UDP scanning, the sniffer container [image](https://github.com/controlplaneio/netassertv2-packet-sniffer) needs `cap_net_raw` capability so that it can bind and read packets from the network interface. As a result, admission controllers or other security mechanisms must be modified to allow the `sniffer` image to run with this capability. Currently, the Security context used by the ephemeral sniffer container looks like the following:\n\n```yaml\n...\n...\n   securityContext:\n     allowPrivilegeEscalation: false\n     capabilities:\n       add:\n       - NET_RAW\n     runAsNonRoot: true\n...\n...\n```\n\n- Although they do not consume any resources, ephemeral containers that are injected as part of the test(s) by `NetAssert` will remain in the Pod specification\n\n- Service meshes are not be currently supported\n\n## E2E Tests\n\n- Please check this [README.md](./e2e/README.md)\n\n[testing_workflow_badge]: https://github.com/controlplaneio/netassert/actions/workflows/build.yaml/badge.svg\n\n[release_workflow_badge]: https://github.com/controlplaneio/netassert/actions/workflows/release.yaml/badge.svg\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Our Security Address\n\nContact: `security@control-plane.io`\nEncryption: `https://keybase.io/sublimino/pgp_keys.asc`\nDisclosure: `Full`\n"
  },
  {
    "path": "cmd/netassert/cli/common.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n\t\"github.com/controlplaneio/netassert/v2/internal/kubeops\"\n\t\"github.com/hashicorp/go-hclog\"\n)\n\n// loadTestCases - Reads test from a file or Directory\nfunc loadTestCases(testCasesFile, testCasesDir string) (data.Tests, error) {\n\tif testCasesFile == \"\" && testCasesDir == \"\" {\n\t\treturn nil, errors.New(\"either an input file or an input dir containing the tests must be provided using \" +\n\t\t\t\"flags (--input-file or --input-dir)\")\n\t}\n\n\tif testCasesFile != \"\" && testCasesDir != \"\" {\n\t\treturn nil, errors.New(\"input must be either a file or a directory but not both i.e use one of \" +\n\t\t\t\"the flags --input-file or --input-dir\")\n\t}\n\n\tvar (\n\t\ttestCases data.Tests\n\t\terr       error\n\t)\n\n\tswitch {\n\tcase testCasesDir != \"\":\n\t\ttestCases, err = data.ReadTestsFromDir(testCasesDir)\n\tcase testCasesFile != \"\":\n\t\ttestCases, err = data.ReadTestsFromFile(testCasesFile)\n\t}\n\n\treturn testCases, err\n}\n\n// createService - creates a new kubernetes operations service\nfunc createService(kubeconfigPath string, l hclog.Logger) (*kubeops.Service, error) {\n\t// if the user has supplied a kubeConfig file location then\n\tif kubeconfigPath != \"\" {\n\t\treturn kubeops.NewServiceFromKubeConfigFile(kubeconfigPath, l)\n\t}\n\n\treturn kubeops.NewDefaultService(l)\n}\n"
  },
  {
    "path": "cmd/netassert/cli/gen_result.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\n// genResult - Prints results to Stdout and writes it to a Tap file\nfunc genResult(testCases data.Tests, tapFile string, lg hclog.Logger) error {\n\tfailedTestCases := 0\n\n\tfor _, v := range testCases {\n\t\t// increment the no. of test cases\n\t\tif v.Pass {\n\t\t\tlg.Info(\"✅ Test Result\", \"Name\", v.Name, \"Pass\", v.Pass)\n\t\t\tcontinue\n\t\t}\n\n\t\tlg.Info(\"❌ Test Result\", \"Name\", v.Name, \"Pass\", v.Pass, \"FailureReason\", v.FailureReason)\n\t\tfailedTestCases++\n\t}\n\n\ttf, err := os.Create(tapFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create tap file %q: %w\", tapFile, err)\n\t}\n\n\tif err := testCases.TAPResult(tf); err != nil {\n\t\treturn fmt.Errorf(\"unable to generate tap results: %w\", err)\n\t}\n\n\tif err := tf.Close(); err != nil {\n\t\treturn fmt.Errorf(\"unable to close tap file %q: %w\", tapFile, err)\n\t}\n\n\tlg.Info(\"✍ Wrote test result in a TAP File\", \"fileName\", tapFile)\n\n\tif failedTestCases > 0 {\n\t\treturn fmt.Errorf(\"total %v test cases have failed\", failedTestCases)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/netassert/cli/main.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t_ \"go.uber.org/automaxprocs\"\n)\n\nfunc main() {\n\tif err := rootCmd.Execute(); err != nil {\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/netassert/cli/ping.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/kubeops\"\n\t\"github.com/controlplaneio/netassert/v2/internal/logger\"\n)\n\nconst (\n\tapiServerHealthEndpoint = `/healthz` // health endpoint for the K8s server\n)\n\ntype pingCmdConfig struct {\n\tKubeConfig  string\n\tPingTimeout time.Duration\n}\n\nvar pingCmdCfg = pingCmdConfig{}\n\nvar pingCmd = &cobra.Command{\n\tUse: \"ping\",\n\tShort: \"pings the K8s API server over HTTP(S) to see if it is alive and also checks if the server has support for \" +\n\t\t\"ephemeral containers.\",\n\tLong: \"pings the K8s API server over HTTP(S) to see if it is alive and also checks if the server has support for \" +\n\t\t\"ephemeral/debug containers.\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tctx, cancel := context.WithTimeout(context.Background(), pingCmdCfg.PingTimeout)\n\t\tdefer cancel()\n\t\tlg := logger.NewHCLogger(\"info\", fmt.Sprintf(\"%s-%s\", appName, version), os.Stdout)\n\t\tk8sSvc, err := createService(pingCmdCfg.KubeConfig, lg)\n\n\t\tif err != nil {\n\t\t\tlg.Error(\"Ping failed, unable to build K8s Client\", \"error\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tping(ctx, lg, k8sSvc)\n\t},\n\tVersion: rootCmd.Version,\n}\n\n// checkEphemeralContainerSupport checks to see if ephemeral containers are supported by the K8s server\nfunc ping(ctx context.Context, lg hclog.Logger, k8sSvc *kubeops.Service) {\n\n\tif err := k8sSvc.PingHealthEndpoint(ctx, apiServerHealthEndpoint); err != nil {\n\t\tlg.Error(\"Ping failed\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tlg.Info(\"✅ Successfully pinged \" + apiServerHealthEndpoint + \" endpoint of the Kubernetes server\")\n\n\tif err := k8sSvc.CheckEphemeralContainerSupport(ctx); err != nil {\n\t\tlg.Error(\"❌ Ephemeral containers are not supported by the Kubernetes server\",\n\t\t\t\"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tlg.Info(\"✅ Ephemeral containers are supported by the Kubernetes server\")\n}\n\nfunc init() {\n\tpingCmd.Flags().DurationVarP(&pingCmdCfg.PingTimeout, \"timeout\", \"t\", 60*time.Second,\n\t\t\"Timeout for the ping command\")\n\tpingCmd.Flags().StringVarP(&pingCmdCfg.KubeConfig, \"kubeconfig\", \"k\", \"\", \"path to kubeconfig file\")\n}\n"
  },
  {
    "path": "cmd/netassert/cli/root.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// these variables are overwritten at build time using ldflags\nvar (\n\tversion           = \"v2.0.0-dev\" // netassert version\n\tappName           = \"NetAssert\"  // name of the application\n\tgitHash           = \"\"           // the git hash of the build\n\tbuildDate         = \"\"           // build date, will be injected by the build system\n\tscannerImgVersion = \"latest\"     // scanner container image version\n\tsnifferImgVersion = \"latest\"     // sniffer container image version\n)\n\nvar rootCmd = &cobra.Command{\n\tUse:   \"netassert\",\n\tShort: \"NetAssert is a command line utility to test network connectivity between kubernetes objects\",\n\tLong: \"NetAssert is a command line utility to test network connectivity between kubernetes objects.\\n\" +\n\t\t\"It currently supports Deployment, Pod, Statefulset and Daemonset.\\nYou can check the traffic flow between these objects or from these \" +\n\t\t\"objects to a remote host or an IP address.\\n\\nBuilt by ControlPlane https://control-plane.io\",\n\n\tVersion: fmt.Sprintf(\"\\nBuilt by ControlPlane https://control-plane.io\\n\"+\n\t\t\"Version: %s\\nCommit Hash: %s\\nBuild Date: %s\\n\",\n\t\tversion, gitHash, buildDate),\n}\n\nfunc init() {\n\t// add our subcommands\n\trootCmd.AddCommand(runCmd)\n\trootCmd.AddCommand(validateCmd)\n\trootCmd.AddCommand(versionCmd)\n\trootCmd.AddCommand(pingCmd)\n}\n"
  },
  {
    "path": "cmd/netassert/cli/run.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/engine\"\n\t\"github.com/controlplaneio/netassert/v2/internal/logger\"\n)\n\n// RunConfig - configuration for the run command\ntype runCmdConfig struct {\n\tTapFile                string\n\tSuffixLength           int\n\tSnifferContainerImage  string\n\tSnifferContainerPrefix string\n\tScannerContainerImage  string\n\tScannerContainerPrefix string\n\tPauseInSeconds         int\n\tPacketCaptureInterface string\n\tKubeConfig             string\n\tTestCasesFile          string\n\tTestCasesDir           string\n\tLogLevel               string\n}\n\n// Initialize with default values\nvar runCmdCfg = runCmdConfig{\n\tTapFile:                \"results.tap\", // name of the default TAP file where the results will be written\n\tSuffixLength:           9,             // suffix length of the random string to be appended to the container name\n\tSnifferContainerImage:  fmt.Sprintf(\"%s:%s\", \"docker.io/controlplane/netassertv2-packet-sniffer\", snifferImgVersion),\n\tSnifferContainerPrefix: \"netassertv2-sniffer\",\n\tScannerContainerImage:  fmt.Sprintf(\"%s:%s\", \"docker.io/controlplane/netassertv2-l4-client\", scannerImgVersion),\n\tScannerContainerPrefix: \"netassertv2-client\",\n\tPauseInSeconds:         1,      // seconds to pause before each test case\n\tPacketCaptureInterface: `eth0`, // the interface used by the sniffer image to capture traffic\n\tLogLevel:               \"info\", // log level\n}\n\nvar runCmd = &cobra.Command{\n\tUse: \"run\",\n\tShort: \"Run the program with the specified source file or source directory. Only one of the two \" +\n\t\t\"flags (--input-file and --input-dir) can be used at a time. The --input-dir \" +\n\t\t\"flag only reads the first level of the directory and does not recursively scan it.\",\n\tLong: \"Run the program with the specified source file or source directory. Only one of the two \" +\n\t\t\"flags (--input-file and --input-dir) can be used at a time. The --input-dir \" +\n\t\t\"flag only reads the first level of the directory and does not recursively scan it.\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tlg := logger.NewHCLogger(runCmdCfg.LogLevel, fmt.Sprintf(\"%s-%s\", appName, version), os.Stdout)\n\t\tif err := runTests(lg); err != nil {\n\t\t\tlg.Error(\" ❌ Failed to successfully run all the tests\", \"error\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t},\n\n\tVersion: rootCmd.Version,\n}\n\n// run - runs the netAssert Test(s)\nfunc runTests(lg hclog.Logger) error {\n\ttestCases, err := loadTestCases(runCmdCfg.TestCasesFile, runCmdCfg.TestCasesDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to load test cases: %w\", err)\n\t}\n\n\t//lg := logger.NewHCLogger(runCmdCfg.LogLevel, fmt.Sprintf(\"%s-%s\", appName, version), os.Stdout)\n\tk8sSvc, err := createService(runCmdCfg.KubeConfig, lg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to build K8s client: %w\", err)\n\t}\n\n\tctx := context.Background()\n\tctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)\n\tdefer cancel()\n\n\t// ping the kubernetes cluster and check to see if\n\t// it is alive and that it has support for ephemeral container(s)\n\tping(ctx, lg, k8sSvc)\n\n\t// initialise our test runner\n\ttestRunner := engine.New(k8sSvc, lg)\n\t// initialise our done signal\n\tdone := make(chan struct{})\n\n\t// add our test runner to the wait group\n\tgo func() {\n\t\tdefer func() {\n\t\t\t// once all our go routines have finished notify the done channel\n\t\t\tdone <- struct{}{}\n\t\t}()\n\n\t\t// run the tests\n\t\ttestRunner.RunTests(\n\t\t\tctx,                              // context to use\n\t\t\ttestCases,                        // net assert test cases\n\t\t\trunCmdCfg.SnifferContainerPrefix, // prefix used for the sniffer container name\n\t\t\trunCmdCfg.SnifferContainerImage,  // sniffer container image location\n\t\t\trunCmdCfg.ScannerContainerPrefix, // scanner container prefix used in the container name\n\t\t\trunCmdCfg.ScannerContainerImage,  // scanner container image location\n\t\t\trunCmdCfg.SuffixLength,           // length of random string that will be appended to the snifferContainerPrefix and scannerContainerPrefix\n\t\t\ttime.Duration(runCmdCfg.PauseInSeconds)*time.Second, // pause duration between each test\n\t\t\trunCmdCfg.PacketCaptureInterface,                    // the interface used by the sniffer image to capture traffic\n\t\t)\n\t}()\n\n\t// Wait for the tests to finish or for the context to be canceled\n\tselect {\n\tcase <-done:\n\t\t// all our tests have finished running\n\tcase <-ctx.Done():\n\t\tlg.Info(\"Received signal from OS\", \"msg\", ctx.Err())\n\t\t// context has been cancelled, we wait for our test runner to finish\n\t\t<-done\n\t}\n\n\treturn genResult(testCases, runCmdCfg.TapFile, lg)\n}\n\nfunc init() {\n\t// Bind flags to the runCmd\n\trunCmd.Flags().StringVarP(&runCmdCfg.TapFile, \"tap\", \"t\", runCmdCfg.TapFile, \"output tap file containing the tests results\")\n\trunCmd.Flags().IntVarP(&runCmdCfg.SuffixLength, \"suffix-length\", \"s\", runCmdCfg.SuffixLength, \"length of the random suffix that will appended to the scanner/sniffer containers\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.SnifferContainerImage, \"sniffer-image\", \"i\", runCmdCfg.SnifferContainerImage, \"container image to be used as sniffer\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.SnifferContainerPrefix, \"sniffer-prefix\", \"p\", runCmdCfg.SnifferContainerPrefix, \"prefix of the sniffer container\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.ScannerContainerImage, \"scanner-image\", \"c\", runCmdCfg.ScannerContainerImage, \"container image to be used as scanner\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.ScannerContainerPrefix, \"scanner-prefix\", \"x\", runCmdCfg.ScannerContainerPrefix, \"prefix of the scanner debug container name\")\n\trunCmd.Flags().IntVarP(&runCmdCfg.PauseInSeconds, \"pause-sec\", \"P\", runCmdCfg.PauseInSeconds, \"number of seconds to pause before running each test case\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.PacketCaptureInterface, \"interface\", \"n\", runCmdCfg.PacketCaptureInterface, \"the network interface used by the sniffer container to capture packets\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.TestCasesFile, \"input-file\", \"f\", runCmdCfg.TestCasesFile, \"input test file that contains a list of netassert tests\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.TestCasesDir, \"input-dir\", \"d\", runCmdCfg.TestCasesDir, \"input test directory that contains a list of netassert test files\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.KubeConfig, \"kubeconfig\", \"k\", runCmdCfg.KubeConfig, \"path to kubeconfig file\")\n\trunCmd.Flags().StringVarP(&runCmdCfg.LogLevel, \"log-level\", \"l\", \"info\", \"set log level (info, debug or trace)\")\n}\n"
  },
  {
    "path": "cmd/netassert/cli/validate.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// validateCmdConfig - config for validate sub-command\ntype validateCmdConfig struct {\n\tTestCasesFile string\n\tTestCasesDir  string\n}\n\nvar (\n\tvalidateCmdCfg validateCmdConfig // config for validate sub-command that will be used in the package\n\n\tvalidateCmd = &cobra.Command{\n\t\tUse: \"validate\",\n\t\tShort: \"verify the syntax and semantic correctness of netassert test(s) in a test file or folder. Only one of the \" +\n\t\t\t\"two flags (--input-file and --input-dir) can be used at a time.\",\n\t\tRun:     validateTestCases,\n\t\tVersion: rootCmd.Version,\n\t}\n)\n\n// validateTestCases - validates test cases from file or directory\nfunc validateTestCases(cmd *cobra.Command, args []string) {\n\n\t_, err := loadTestCases(validateCmdCfg.TestCasesFile, validateCmdCfg.TestCasesDir)\n\tif err != nil {\n\t\tfmt.Println(\"❌ Validation of test cases failed\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfmt.Println(\"✅ All test cases are valid syntax-wise and semantically\")\n}\n\nfunc init() {\n\tvalidateCmd.Flags().StringVarP(&validateCmdCfg.TestCasesFile, \"input-file\", \"f\", \"\", \"input test file that contains a list of netassert tests\")\n\tvalidateCmd.Flags().StringVarP(&validateCmdCfg.TestCasesDir, \"input-dir\", \"d\", \"\", \"input test directory that contains a list of netassert test files\")\n}\n"
  },
  {
    "path": "cmd/netassert/cli/version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/logger\"\n)\n\nvar versionCmd = &cobra.Command{\n\tUse:          \"version\",\n\tShort:        \"Prints the version and other details associated with the program\",\n\tSilenceUsage: false,\n\tRun:          versionDetails,\n}\n\n// versionDetails - prints build information to the STDOUT\nfunc versionDetails(cmd *cobra.Command, args []string) {\n\troot := cmd.Root()\n\troot.SetArgs([]string{\"--version\"})\n\tif err := root.Execute(); err != nil {\n\t\tlg := logger.NewHCLogger(runCmdCfg.LogLevel, fmt.Sprintf(\"%s-%s\", appName, version), os.Stdout)\n\t\tlg.Error(\"Failed to get version details\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "download.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\nUSER='controlplaneio'\nREPO='netassert'\nBINARY='netassert'\nPWD=$(pwd)\nLATEST=$(curl --silent \"https://api.github.com/repos/$USER/$REPO/releases/latest\" | grep '\"tag_name\":' | cut -d'\"' -f4)\necho \"Found latest release: $LATEST\"\nOS=$(uname -s | tr '[:upper:]' '[:lower:]')\necho \"OS: $OS\"\nARCH=$(uname -m)\nif [[ \"$ARCH\" == \"x86_64\" ]]; then\n  ARCH=\"amd64\"\nfi\necho \"ARCH: $ARCH\"\nFILE=\"${BINARY}_${LATEST}_${OS}_${ARCH}.tar.gz\"\nDOWNLOAD_URL=\"https://github.com/controlplaneio/${REPO}/releases/download/${LATEST}/${FILE}\"\nCHECKSUM_URL=\"https://github.com/controlplaneio/${REPO}/releases/download/${LATEST}/checksums-sha256.txt\"\necho \"[+] Downloading latest checksums from ${CHECKSUM_URL}\"\nif ! curl -sfLo \"checksums.txt\" \"$CHECKSUM_URL\"; then\n  echo \"Failed to download checksums\"\n  exit 1\nfi\necho \"[+] Downloading latest tarball from ${DOWNLOAD_URL}\"\nif ! curl -sfLO \"$DOWNLOAD_URL\"; then\n  echo \"Failed to download tarball\"\n  exit 1\nfi\necho \"[+] Verifying checksums\"\nif ! sha256sum -c checksums.txt --ignore-missing; then\n  echo \"[+] Checksum verification failed\"\n  exit 1\nfi\necho \"[+] Downloaded file verified successfully\"\n## unzip the tarball\necho \"[+] Unzipping the downloaded tarball in directory ${PWD}\"\nif ! tar -xzf \"${FILE}\"; then\n  echo \"[+] Failed to unzip the downloaded tarball\"\n  exit 1\nfi\necho \"[+] Downloaded file unzipped successfully\"\nif [[ ! -f \"${BINARY}\" ]]; then\n  echo \"[+] ${BINARY} file was not found in the current path\"\n  exit 1\nfi\necho \"[+] You can now run netassert from ${PWD}/${BINARY}\""
  },
  {
    "path": "e2e/README.md",
    "content": "# End-to-End(E2E) Tests\n\nThe E2E tests uses `terraform` and `terratest` to spin up GKE and EKS clusters. There are altogether four tests:\n\n- AWS EKS 1.34 with AWS VPC CNI\n  - Test AWS EKS 1.34 with the default AWS VPC CNI that support Network Policies\n- AWS EKS 1.34 with Calico CNI\n  - Test AWS EKS 1.34 with calico CNI v3.35.0. As part of the test, the AWS CNI is uninstalled and Calico is installed\n- GCP GKE 1.33 with GCP VPC CNI\n  - GKE 1.33 with GCP VPC CNI\n- GCP GKE 1.33 GCP Dataplane v2\n  - GKE 1.33 with data-plane v2 based on Cilium\n- Kind k8s 1.35 with Calico CNI\n  - Kind with Calico CNI\n\n*Each test is skipped if the corresponding environment variable is not set.*\n\n| Test                                | Environment variable |\n|-------------------------------------|----------------------|\n| AWS EKS with AWS VPC CNI            | EKS_VPC_E2E_TESTS    |\n| AWS EKS with Calico CNI             | EKS_CALICO_E2E_TESTS |\n| GCP GKE with GCP VPC CNI            | GKE_VPC_E2E_TESTS    |\n| GCP GKE GCP with DataPlane V2       | GKE_DPV2_E2E_TESTS   |\n| Kind with Calico CNI                | KIND_E2E_TESTS       |\n\n## Running tests\n\n- Make sure you have installed `kubectl` and `AWS Cli v2`\n- Make sure you have also installed `gke-gcloud-auth-plugin` for kubectl by following this [link](https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke)\n\nFor AWS EKS tests, make sure you have valid AWS credentials:\n\n```bash\n❯ aws sso login\n\n❯ export EKS_VPC_E2E_TESTS=yes\n❯ export EKS_CALICO_E2E_TESTS=yes\n```\n\nFor GCP GKE tests, make sure you export the Project name and set it as the default project:\n\n```bash\n❯ export GOOGLE_PROJECT=<your_project>\n❯ gcloud config set project <your_project>\n❯ gcloud auth application-default login\n❯ export GKE_VPC_E2E_TESTS=yes\n❯ export GKE_DPV2_E2E_TESTS=yes\n```\n\nRun the tests using the following command:\n\n```bash\n# from the root of the project\n❯ go test -timeout=91m -v ./e2e/... -count=1\n```\n\nTests can be configured by updating values in [end-to-end test helpers](./helpers/)\n\n## Azure AKS Integration\n\nCurrently, end-to-end testing of NetAssert with Azure Kubernetes Service (AKS) is not scheduled. However, we do not foresee any architectural reasons that would prevent successful integration.\n\n### Network Policy Support\nThere are [three primary approaches](https://learn.microsoft.com/en-us/azure/aks/use-network-policies) for supporting network policies in AKS.\n\nIf the requirement is limited to **Linux nodes only** (excluding Windows), the recommended solution is [Azure CNI powered by Cilium](https://learn.microsoft.com/en-us/azure/aks/azure-cni-powered-by-cilium).\n\n### Deployment via Terraform\nFor deploying a testing cluster, the Container Network Interface (CNI) configuration appears straightforward. It can likely be handled via a single parameter in the `azurerm` provider, specifically the [`network_policy` argument](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#network_policy-1). \n\n*Note: This Terraform configuration has yet to be validated.*"
  },
  {
    "path": "e2e/clusters/aws-eks-terraform-module/eks.tf",
    "content": "provider \"aws\" {\n  region = var.region\n}\n\ndata \"aws_availability_zones\" \"available\" {}\n\n\n\nmodule \"eks\" {\n  source  = \"terraform-aws-modules/eks/aws\"\n  version = \"~> 20.0\"\n  #version = \"~> 19\"\n  \n\n  cluster_name    = var.cluster_name\n  cluster_version = var.cluster_version\n\n  vpc_id                         = module.vpc.vpc_id\n  subnet_ids                     = module.vpc.private_subnets\n  cluster_endpoint_public_access = true\n  enable_cluster_creator_admin_permissions = true\n\n  cluster_addons = {\n    vpc-cni = {\n      before_compute = true\n      most_recent    = true\n      configuration_values = jsonencode({\n        #resolve_conflicts_on_update = \"OVERWRITE\"\n        enableNetworkPolicy = var.enable_vpc_network_policies ? \"true\" : \"false\"\n      })\n    }\n  }\n\n  eks_managed_node_groups = {\n    example = {\n      name = \"${var.node_group_name}1\"\n\n      # Starting on 1.30, AL2023 is the default AMI type for EKS managed node groups\n      ami_type       = \"AL2023_x86_64_STANDARD\"\n      instance_types = [\"t3.medium\"]\n\n      min_size     = 0\n      max_size     = 3\n      desired_size = var.desired_size\n    }\n  }\n\n  # Extend node-to-node security group rules\n  node_security_group_additional_rules = {\n    ingress_self_all = {\n      description = \"Node to node all ports/protocols\"\n      protocol    = \"-1\"\n      from_port   = 0\n      to_port     = 0\n      type        = \"ingress\"\n      self        = true\n    }\n    \n    egress_all = {\n      description      = \"Node all egress\"\n      protocol         = \"-1\"\n      from_port        = 0\n      to_port          = 0\n      type             = \"egress\"\n      cidr_blocks      = [\"0.0.0.0/0\"]\n      ipv6_cidr_blocks = [\"::/0\"]\n    }\n  }\n}\n\nresource \"null_resource\" \"generate_kubeconfig\" {\n  depends_on = [module.eks]\n\n  provisioner \"local-exec\" {\n    command = \"aws eks update-kubeconfig --region ${var.region} --name ${module.eks.cluster_name} --kubeconfig ${var.kubeconfig_file}\"\n  }\n}\n"
  },
  {
    "path": "e2e/clusters/aws-eks-terraform-module/outputs.tf",
    "content": "output \"cluster_endpoint\" {\n  description = \"Endpoint for EKS control plane\"\n  value       = module.eks.cluster_endpoint\n}\n\noutput \"cluster_security_group_id\" {\n  description = \"Security group ids attached to the cluster control plane\"\n  value       = module.eks.cluster_security_group_id\n}\n\noutput \"region\" {\n  description = \"AWS region\"\n  value       = var.region\n}\n\noutput \"cluster_name\" {\n  description = \"Kubernetes Cluster Name\"\n  value       = module.eks.cluster_name\n}\n"
  },
  {
    "path": "e2e/clusters/aws-eks-terraform-module/variables.tf",
    "content": "variable \"region\" {\n  description = \"AWS region\"\n  type        = string\n}\n\nvariable \"cluster_version\" {\n  description = \"The AWS EKS cluster version\"\n  type        = string\n}\n\nvariable \"cluster_name\" {\n  type        = string\n  description = \"name of the cluster and VPC\"\n}\n\nvariable \"kubeconfig_file\" {\n  type        = string\n  description = \"name of the file that contains the kubeconfig information\"\n  default     = \".kubeconfig\"\n}\n\nvariable \"desired_size\" {\n  type        = number\n  description = \"desired size of the worker node pool\"\n  default     = 0\n}\n\nvariable \"node_group_name\" {\n  type        = string\n  description = \"prefix of the node group\"\n  default     = \"group\"\n}\n\nvariable \"enable_vpc_network_policies\" {\n  type        = bool\n  description = \"enable or disable vpc network policies\"\n}\n"
  },
  {
    "path": "e2e/clusters/aws-eks-terraform-module/vpc.tf",
    "content": "module \"vpc\" {\n  source  = \"terraform-aws-modules/vpc/aws\"\n\n  // set VPC name same as the EKS cluster name\n  name = var.cluster_name\n  version = \"~> 5.0\"\n\n  cidr = \"10.0.0.0/16\"\n  azs  = slice(data.aws_availability_zones.available.names, 0, 3)\n\n  private_subnets = [\"10.0.1.0/24\", \"10.0.2.0/24\", \"10.0.3.0/24\"]\n  public_subnets  = [\"10.0.4.0/24\", \"10.0.5.0/24\", \"10.0.6.0/24\"]\n\n  enable_nat_gateway   = true\n  single_nat_gateway   = true\n  enable_dns_hostnames = true\n  enable_dns_support   = true\n\n  public_subnet_tags = {\n    \"kubernetes.io/cluster/${var.cluster_name}\" = \"shared\"\n    \"kubernetes.io/role/elb\"                    = 1\n  }\n\n  private_subnet_tags = {\n    \"kubernetes.io/cluster/${var.cluster_name}\" = \"shared\"\n    \"kubernetes.io/role/internal-elb\"           = 1\n  }\n\n  tags = {\n    owner       = \"prefix\"\n    environment = \"test\"\n  }\n}\n"
  },
  {
    "path": "e2e/clusters/eks-with-calico-cni/calico-3.26.4.yaml",
    "content": "---\n# Source: calico/templates/calico-kube-controllers.yaml\n# This manifest creates a Pod Disruption Budget for Controller to allow K8s Cluster Autoscaler to evict\n\napiVersion: policy/v1\nkind: PodDisruptionBudget\nmetadata:\n  name: calico-kube-controllers\n  namespace: kube-system\n  labels:\n    k8s-app: calico-kube-controllers\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      k8s-app: calico-kube-controllers\n---\n# Source: calico/templates/calico-kube-controllers.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: calico-kube-controllers\n  namespace: kube-system\n---\n# Source: calico/templates/calico-node.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: calico-node\n  namespace: kube-system\n---\n# Source: calico/templates/calico-node.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: calico-cni-plugin\n  namespace: kube-system\n---\n# Source: calico/templates/calico-config.yaml\n# This ConfigMap is used to configure a self-hosted Calico installation.\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: calico-config\n  namespace: kube-system\ndata:\n  # Typha is disabled.\n  typha_service_name: \"none\"\n  # Configure the backend to use.\n  calico_backend: \"bird\"\n\n  # Configure the MTU to use for workload interfaces and tunnels.\n  # By default, MTU is auto-detected, and explicitly setting this field should not be required.\n  # You can override auto-detection by providing a non-zero value.\n  veth_mtu: \"0\"\n\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  cni_network_config: |-\n    {\n      \"name\": \"k8s-pod-network\",\n      \"cniVersion\": \"0.3.1\",\n      \"plugins\": [\n        {\n          \"type\": \"calico\",\n          \"log_level\": \"info\",\n          \"log_file_path\": \"/var/log/calico/cni/cni.log\",\n          \"datastore_type\": \"kubernetes\",\n          \"nodename\": \"__KUBERNETES_NODE_NAME__\",\n          \"mtu\": __CNI_MTU__,\n          \"ipam\": {\n              \"type\": \"calico-ipam\"\n          },\n          \"policy\": {\n              \"type\": \"k8s\"\n          },\n          \"kubernetes\": {\n              \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n          }\n        },\n        {\n          \"type\": \"portmap\",\n          \"snat\": true,\n          \"capabilities\": {\"portMappings\": true}\n        },\n        {\n          \"type\": \"bandwidth\",\n          \"capabilities\": {\"bandwidth\": true}\n        }\n      ]\n    }\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: bgpconfigurations.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: BGPConfiguration\n    listKind: BGPConfigurationList\n    plural: bgpconfigurations\n    singular: bgpconfiguration\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: BGPConfiguration contains the configuration for any BGP routing.\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: BGPConfigurationSpec contains the values of the BGP configuration.\n            properties:\n              asNumber:\n                description: 'ASNumber is the default AS number used by a node. [Default:\n                  64512]'\n                format: int32\n                type: integer\n              bindMode:\n                description: BindMode indicates whether to listen for BGP connections\n                  on all addresses (None) or only on the node's canonical IP address\n                  Node.Spec.BGP.IPvXAddress (NodeIP). Default behaviour is to listen\n                  for BGP connections on all addresses.\n                type: string\n              communities:\n                description: Communities is a list of BGP community values and their\n                  arbitrary names for tagging routes.\n                items:\n                  description: Community contains standard or large community value\n                    and its name.\n                  properties:\n                    name:\n                      description: Name given to community value.\n                      type: string\n                    value:\n                      description: Value must be of format `aa:nn` or `aa:nn:mm`.\n                        For standard community use `aa:nn` format, where `aa` and\n                        `nn` are 16 bit number. For large community use `aa:nn:mm`\n                        format, where `aa`, `nn` and `mm` are 32 bit number. Where,\n                        `aa` is an AS Number, `nn` and `mm` are per-AS identifier.\n                      pattern: ^(\\d+):(\\d+)$|^(\\d+):(\\d+):(\\d+)$\n                      type: string\n                  type: object\n                type: array\n              ignoredInterfaces:\n                description: IgnoredInterfaces indicates the network interfaces that\n                  needs to be excluded when reading device routes.\n                items:\n                  type: string\n                type: array\n              listenPort:\n                description: ListenPort is the port where BGP protocol should listen.\n                  Defaults to 179\n                maximum: 65535\n                minimum: 1\n                type: integer\n              logSeverityScreen:\n                description: 'LogSeverityScreen is the log severity above which logs\n                  are sent to the stdout. [Default: INFO]'\n                type: string\n              nodeMeshMaxRestartTime:\n                description: Time to allow for software restart for node-to-mesh peerings.  When\n                  specified, this is configured as the graceful restart timeout.  When\n                  not specified, the BIRD default of 120s is used. This field can\n                  only be set on the default BGPConfiguration instance and requires\n                  that NodeMesh is enabled\n                type: string\n              nodeMeshPassword:\n                description: Optional BGP password for full node-to-mesh peerings.\n                  This field can only be set on the default BGPConfiguration instance\n                  and requires that NodeMesh is enabled\n                properties:\n                  secretKeyRef:\n                    description: Selects a key of a secret in the node pod's namespace.\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                type: object\n              nodeToNodeMeshEnabled:\n                description: 'NodeToNodeMeshEnabled sets whether full node to node\n                  BGP mesh is enabled. [Default: true]'\n                type: boolean\n              prefixAdvertisements:\n                description: PrefixAdvertisements contains per-prefix advertisement\n                  configuration.\n                items:\n                  description: PrefixAdvertisement configures advertisement properties\n                    for the specified CIDR.\n                  properties:\n                    cidr:\n                      description: CIDR for which properties should be advertised.\n                      type: string\n                    communities:\n                      description: Communities can be list of either community names\n                        already defined in `Specs.Communities` or community value\n                        of format `aa:nn` or `aa:nn:mm`. For standard community use\n                        `aa:nn` format, where `aa` and `nn` are 16 bit number. For\n                        large community use `aa:nn:mm` format, where `aa`, `nn` and\n                        `mm` are 32 bit number. Where,`aa` is an AS Number, `nn` and\n                        `mm` are per-AS identifier.\n                      items:\n                        type: string\n                      type: array\n                  type: object\n                type: array\n              serviceClusterIPs:\n                description: ServiceClusterIPs are the CIDR blocks from which service\n                  cluster IPs are allocated. If specified, Calico will advertise these\n                  blocks, as well as any cluster IPs within them.\n                items:\n                  description: ServiceClusterIPBlock represents a single allowed ClusterIP\n                    CIDR block.\n                  properties:\n                    cidr:\n                      type: string\n                  type: object\n                type: array\n              serviceExternalIPs:\n                description: ServiceExternalIPs are the CIDR blocks for Kubernetes\n                  Service External IPs. Kubernetes Service ExternalIPs will only be\n                  advertised if they are within one of these blocks.\n                items:\n                  description: ServiceExternalIPBlock represents a single allowed\n                    External IP CIDR block.\n                  properties:\n                    cidr:\n                      type: string\n                  type: object\n                type: array\n              serviceLoadBalancerIPs:\n                description: ServiceLoadBalancerIPs are the CIDR blocks for Kubernetes\n                  Service LoadBalancer IPs. Kubernetes Service status.LoadBalancer.Ingress\n                  IPs will only be advertised if they are within one of these blocks.\n                items:\n                  description: ServiceLoadBalancerIPBlock represents a single allowed\n                    LoadBalancer IP CIDR block.\n                  properties:\n                    cidr:\n                      type: string\n                  type: object\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: (devel)\n  creationTimestamp: null\n  name: bgpfilters.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: BGPFilter\n    listKind: BGPFilterList\n    plural: bgpfilters\n    singular: bgpfilter\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: BGPFilterSpec contains the IPv4 and IPv6 filter rules of\n              the BGP Filter.\n            properties:\n              exportV4:\n                description: The ordered set of IPv4 BGPFilter rules acting on exporting\n                  routes to a peer.\n                items:\n                  description: BGPFilterRuleV4 defines a BGP filter rule consisting\n                    a single IPv4 CIDR block and a filter action for this CIDR.\n                  properties:\n                    action:\n                      type: string\n                    cidr:\n                      type: string\n                    matchOperator:\n                      type: string\n                  required:\n                  - action\n                  - cidr\n                  - matchOperator\n                  type: object\n                type: array\n              exportV6:\n                description: The ordered set of IPv6 BGPFilter rules acting on exporting\n                  routes to a peer.\n                items:\n                  description: BGPFilterRuleV6 defines a BGP filter rule consisting\n                    a single IPv6 CIDR block and a filter action for this CIDR.\n                  properties:\n                    action:\n                      type: string\n                    cidr:\n                      type: string\n                    matchOperator:\n                      type: string\n                  required:\n                  - action\n                  - cidr\n                  - matchOperator\n                  type: object\n                type: array\n              importV4:\n                description: The ordered set of IPv4 BGPFilter rules acting on importing\n                  routes from a peer.\n                items:\n                  description: BGPFilterRuleV4 defines a BGP filter rule consisting\n                    a single IPv4 CIDR block and a filter action for this CIDR.\n                  properties:\n                    action:\n                      type: string\n                    cidr:\n                      type: string\n                    matchOperator:\n                      type: string\n                  required:\n                  - action\n                  - cidr\n                  - matchOperator\n                  type: object\n                type: array\n              importV6:\n                description: The ordered set of IPv6 BGPFilter rules acting on importing\n                  routes from a peer.\n                items:\n                  description: BGPFilterRuleV6 defines a BGP filter rule consisting\n                    a single IPv6 CIDR block and a filter action for this CIDR.\n                  properties:\n                    action:\n                      type: string\n                    cidr:\n                      type: string\n                    matchOperator:\n                      type: string\n                  required:\n                  - action\n                  - cidr\n                  - matchOperator\n                  type: object\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: bgppeers.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: BGPPeer\n    listKind: BGPPeerList\n    plural: bgppeers\n    singular: bgppeer\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: BGPPeerSpec contains the specification for a BGPPeer resource.\n            properties:\n              asNumber:\n                description: The AS Number of the peer.\n                format: int32\n                type: integer\n              filters:\n                description: The ordered set of BGPFilters applied on this BGP peer.\n                items:\n                  type: string\n                type: array\n              keepOriginalNextHop:\n                description: Option to keep the original nexthop field when routes\n                  are sent to a BGP Peer. Setting \"true\" configures the selected BGP\n                  Peers node to use the \"next hop keep;\" instead of \"next hop self;\"(default)\n                  in the specific branch of the Node on \"bird.cfg\".\n                type: boolean\n              maxRestartTime:\n                description: Time to allow for software restart.  When specified,\n                  this is configured as the graceful restart timeout.  When not specified,\n                  the BIRD default of 120s is used.\n                type: string\n              node:\n                description: The node name identifying the Calico node instance that\n                  is targeted by this peer. If this is not set, and no nodeSelector\n                  is specified, then this BGP peer selects all nodes in the cluster.\n                type: string\n              nodeSelector:\n                description: Selector for the nodes that should have this peering.  When\n                  this is set, the Node field must be empty.\n                type: string\n              numAllowedLocalASNumbers:\n                description: Maximum number of local AS numbers that are allowed in\n                  the AS path for received routes. This removes BGP loop prevention\n                  and should only be used if absolutely necesssary.\n                format: int32\n                type: integer\n              password:\n                description: Optional BGP password for the peerings generated by this\n                  BGPPeer resource.\n                properties:\n                  secretKeyRef:\n                    description: Selects a key of a secret in the node pod's namespace.\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                type: object\n              peerIP:\n                description: The IP address of the peer followed by an optional port\n                  number to peer with. If port number is given, format should be `[<IPv6>]:port`\n                  or `<IPv4>:<port>` for IPv4. If optional port number is not set,\n                  and this peer IP and ASNumber belongs to a calico/node with ListenPort\n                  set in BGPConfiguration, then we use that port to peer.\n                type: string\n              peerSelector:\n                description: Selector for the remote nodes to peer with.  When this\n                  is set, the PeerIP and ASNumber fields must be empty.  For each\n                  peering between the local node and selected remote nodes, we configure\n                  an IPv4 peering if both ends have NodeBGPSpec.IPv4Address specified,\n                  and an IPv6 peering if both ends have NodeBGPSpec.IPv6Address specified.  The\n                  remote AS number comes from the remote node's NodeBGPSpec.ASNumber,\n                  or the global default if that is not set.\n                type: string\n              reachableBy:\n                description: Add an exact, i.e. /32, static route toward peer IP in\n                  order to prevent route flapping. ReachableBy contains the address\n                  of the gateway which peer can be reached by.\n                type: string\n              sourceAddress:\n                description: Specifies whether and how to configure a source address\n                  for the peerings generated by this BGPPeer resource.  Default value\n                  \"UseNodeIP\" means to configure the node IP as the source address.  \"None\"\n                  means not to configure a source address.\n                type: string\n              ttlSecurity:\n                description: TTLSecurity enables the generalized TTL security mechanism\n                  (GTSM) which protects against spoofed packets by ignoring received\n                  packets with a smaller than expected TTL value. The provided value\n                  is the number of hops (edges) between the peers.\n                type: integer\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: blockaffinities.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: BlockAffinity\n    listKind: BlockAffinityList\n    plural: blockaffinities\n    singular: blockaffinity\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: BlockAffinitySpec contains the specification for a BlockAffinity\n              resource.\n            properties:\n              cidr:\n                type: string\n              deleted:\n                description: Deleted indicates that this block affinity is being deleted.\n                  This field is a string for compatibility with older releases that\n                  mistakenly treat this field as a string.\n                type: string\n              node:\n                type: string\n              state:\n                type: string\n            required:\n            - cidr\n            - deleted\n            - node\n            - state\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: (devel)\n  creationTimestamp: null\n  name: caliconodestatuses.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: CalicoNodeStatus\n    listKind: CalicoNodeStatusList\n    plural: caliconodestatuses\n    singular: caliconodestatus\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: CalicoNodeStatusSpec contains the specification for a CalicoNodeStatus\n              resource.\n            properties:\n              classes:\n                description: Classes declares the types of information to monitor\n                  for this calico/node, and allows for selective status reporting\n                  about certain subsets of information.\n                items:\n                  type: string\n                type: array\n              node:\n                description: The node name identifies the Calico node instance for\n                  node status.\n                type: string\n              updatePeriodSeconds:\n                description: UpdatePeriodSeconds is the period at which CalicoNodeStatus\n                  should be updated. Set to 0 to disable CalicoNodeStatus refresh.\n                  Maximum update period is one day.\n                format: int32\n                type: integer\n            type: object\n          status:\n            description: CalicoNodeStatusStatus defines the observed state of CalicoNodeStatus.\n              No validation needed for status since it is updated by Calico.\n            properties:\n              agent:\n                description: Agent holds agent status on the node.\n                properties:\n                  birdV4:\n                    description: BIRDV4 represents the latest observed status of bird4.\n                    properties:\n                      lastBootTime:\n                        description: LastBootTime holds the value of lastBootTime\n                          from bird.ctl output.\n                        type: string\n                      lastReconfigurationTime:\n                        description: LastReconfigurationTime holds the value of lastReconfigTime\n                          from bird.ctl output.\n                        type: string\n                      routerID:\n                        description: Router ID used by bird.\n                        type: string\n                      state:\n                        description: The state of the BGP Daemon.\n                        type: string\n                      version:\n                        description: Version of the BGP daemon\n                        type: string\n                    type: object\n                  birdV6:\n                    description: BIRDV6 represents the latest observed status of bird6.\n                    properties:\n                      lastBootTime:\n                        description: LastBootTime holds the value of lastBootTime\n                          from bird.ctl output.\n                        type: string\n                      lastReconfigurationTime:\n                        description: LastReconfigurationTime holds the value of lastReconfigTime\n                          from bird.ctl output.\n                        type: string\n                      routerID:\n                        description: Router ID used by bird.\n                        type: string\n                      state:\n                        description: The state of the BGP Daemon.\n                        type: string\n                      version:\n                        description: Version of the BGP daemon\n                        type: string\n                    type: object\n                type: object\n              bgp:\n                description: BGP holds node BGP status.\n                properties:\n                  numberEstablishedV4:\n                    description: The total number of IPv4 established bgp sessions.\n                    type: integer\n                  numberEstablishedV6:\n                    description: The total number of IPv6 established bgp sessions.\n                    type: integer\n                  numberNotEstablishedV4:\n                    description: The total number of IPv4 non-established bgp sessions.\n                    type: integer\n                  numberNotEstablishedV6:\n                    description: The total number of IPv6 non-established bgp sessions.\n                    type: integer\n                  peersV4:\n                    description: PeersV4 represents IPv4 BGP peers status on the node.\n                    items:\n                      description: CalicoNodePeer contains the status of BGP peers\n                        on the node.\n                      properties:\n                        peerIP:\n                          description: IP address of the peer whose condition we are\n                            reporting.\n                          type: string\n                        since:\n                          description: Since the state or reason last changed.\n                          type: string\n                        state:\n                          description: State is the BGP session state.\n                          type: string\n                        type:\n                          description: Type indicates whether this peer is configured\n                            via the node-to-node mesh, or via en explicit global or\n                            per-node BGPPeer object.\n                          type: string\n                      type: object\n                    type: array\n                  peersV6:\n                    description: PeersV6 represents IPv6 BGP peers status on the node.\n                    items:\n                      description: CalicoNodePeer contains the status of BGP peers\n                        on the node.\n                      properties:\n                        peerIP:\n                          description: IP address of the peer whose condition we are\n                            reporting.\n                          type: string\n                        since:\n                          description: Since the state or reason last changed.\n                          type: string\n                        state:\n                          description: State is the BGP session state.\n                          type: string\n                        type:\n                          description: Type indicates whether this peer is configured\n                            via the node-to-node mesh, or via en explicit global or\n                            per-node BGPPeer object.\n                          type: string\n                      type: object\n                    type: array\n                required:\n                - numberEstablishedV4\n                - numberEstablishedV6\n                - numberNotEstablishedV4\n                - numberNotEstablishedV6\n                type: object\n              lastUpdated:\n                description: LastUpdated is a timestamp representing the server time\n                  when CalicoNodeStatus object last updated. It is represented in\n                  RFC3339 form and is in UTC.\n                format: date-time\n                nullable: true\n                type: string\n              routes:\n                description: Routes reports routes known to the Calico BGP daemon\n                  on the node.\n                properties:\n                  routesV4:\n                    description: RoutesV4 represents IPv4 routes on the node.\n                    items:\n                      description: CalicoNodeRoute contains the status of BGP routes\n                        on the node.\n                      properties:\n                        destination:\n                          description: Destination of the route.\n                          type: string\n                        gateway:\n                          description: Gateway for the destination.\n                          type: string\n                        interface:\n                          description: Interface for the destination\n                          type: string\n                        learnedFrom:\n                          description: LearnedFrom contains information regarding\n                            where this route originated.\n                          properties:\n                            peerIP:\n                              description: If sourceType is NodeMesh or BGPPeer, IP\n                                address of the router that sent us this route.\n                              type: string\n                            sourceType:\n                              description: Type of the source where a route is learned\n                                from.\n                              type: string\n                          type: object\n                        type:\n                          description: Type indicates if the route is being used for\n                            forwarding or not.\n                          type: string\n                      type: object\n                    type: array\n                  routesV6:\n                    description: RoutesV6 represents IPv6 routes on the node.\n                    items:\n                      description: CalicoNodeRoute contains the status of BGP routes\n                        on the node.\n                      properties:\n                        destination:\n                          description: Destination of the route.\n                          type: string\n                        gateway:\n                          description: Gateway for the destination.\n                          type: string\n                        interface:\n                          description: Interface for the destination\n                          type: string\n                        learnedFrom:\n                          description: LearnedFrom contains information regarding\n                            where this route originated.\n                          properties:\n                            peerIP:\n                              description: If sourceType is NodeMesh or BGPPeer, IP\n                                address of the router that sent us this route.\n                              type: string\n                            sourceType:\n                              description: Type of the source where a route is learned\n                                from.\n                              type: string\n                          type: object\n                        type:\n                          description: Type indicates if the route is being used for\n                            forwarding or not.\n                          type: string\n                      type: object\n                    type: array\n                type: object\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: clusterinformations.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: ClusterInformation\n    listKind: ClusterInformationList\n    plural: clusterinformations\n    singular: clusterinformation\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: ClusterInformation contains the cluster specific information.\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: ClusterInformationSpec contains the values of describing\n              the cluster.\n            properties:\n              calicoVersion:\n                description: CalicoVersion is the version of Calico that the cluster\n                  is running\n                type: string\n              clusterGUID:\n                description: ClusterGUID is the GUID of the cluster\n                type: string\n              clusterType:\n                description: ClusterType describes the type of the cluster\n                type: string\n              datastoreReady:\n                description: DatastoreReady is used during significant datastore migrations\n                  to signal to components such as Felix that it should wait before\n                  accessing the datastore.\n                type: boolean\n              variant:\n                description: Variant declares which variant of Calico should be active.\n                type: string\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: felixconfigurations.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: FelixConfiguration\n    listKind: FelixConfigurationList\n    plural: felixconfigurations\n    singular: felixconfiguration\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Felix Configuration contains the configuration for Felix.\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: FelixConfigurationSpec contains the values of the Felix configuration.\n            properties:\n              allowIPIPPacketsFromWorkloads:\n                description: 'AllowIPIPPacketsFromWorkloads controls whether Felix\n                  will add a rule to drop IPIP encapsulated traffic from workloads\n                  [Default: false]'\n                type: boolean\n              allowVXLANPacketsFromWorkloads:\n                description: 'AllowVXLANPacketsFromWorkloads controls whether Felix\n                  will add a rule to drop VXLAN encapsulated traffic from workloads\n                  [Default: false]'\n                type: boolean\n              awsSrcDstCheck:\n                description: 'Set source-destination-check on AWS EC2 instances. Accepted\n                  value must be one of \"DoNothing\", \"Enable\" or \"Disable\". [Default:\n                  DoNothing]'\n                enum:\n                - DoNothing\n                - Enable\n                - Disable\n                type: string\n              bpfConnectTimeLoadBalancingEnabled:\n                description: 'BPFConnectTimeLoadBalancingEnabled when in BPF mode,\n                  controls whether Felix installs the connection-time load balancer.  The\n                  connect-time load balancer is required for the host to be able to\n                  reach Kubernetes services and it improves the performance of pod-to-service\n                  connections.  The only reason to disable it is for debugging purposes.  [Default:\n                  true]'\n                type: boolean\n              bpfDSROptoutCIDRs:\n                description: BPFDSROptoutCIDRs is a list of CIDRs which are excluded\n                  from DSR. That is, clients in those CIDRs will accesses nodeports\n                  as if BPFExternalServiceMode was set to Tunnel.\n                items:\n                  type: string\n                type: array\n              bpfDataIfacePattern:\n                description: BPFDataIfacePattern is a regular expression that controls\n                  which interfaces Felix should attach BPF programs to in order to\n                  catch traffic to/from the network.  This needs to match the interfaces\n                  that Calico workload traffic flows over as well as any interfaces\n                  that handle incoming traffic to nodeports and services from outside\n                  the cluster.  It should not match the workload interfaces (usually\n                  named cali...).\n                type: string\n              bpfDisableUnprivileged:\n                description: 'BPFDisableUnprivileged, if enabled, Felix sets the kernel.unprivileged_bpf_disabled\n                  sysctl to disable unprivileged use of BPF.  This ensures that unprivileged\n                  users cannot access Calico''s BPF maps and cannot insert their own\n                  BPF programs to interfere with Calico''s. [Default: true]'\n                type: boolean\n              bpfEnabled:\n                description: 'BPFEnabled, if enabled Felix will use the BPF dataplane.\n                  [Default: false]'\n                type: boolean\n              bpfEnforceRPF:\n                description: 'BPFEnforceRPF enforce strict RPF on all host interfaces\n                  with BPF programs regardless of what is the per-interfaces or global\n                  setting. Possible values are Disabled, Strict or Loose. [Default:\n                  Loose]'\n                type: string\n              bpfExtToServiceConnmark:\n                description: 'BPFExtToServiceConnmark in BPF mode, control a 32bit\n                  mark that is set on connections from an external client to a local\n                  service. This mark allows us to control how packets of that connection\n                  are routed within the host and how is routing interpreted by RPF\n                  check. [Default: 0]'\n                type: integer\n              bpfExternalServiceMode:\n                description: 'BPFExternalServiceMode in BPF mode, controls how connections\n                  from outside the cluster to services (node ports and cluster IPs)\n                  are forwarded to remote workloads.  If set to \"Tunnel\" then both\n                  request and response traffic is tunneled to the remote node.  If\n                  set to \"DSR\", the request traffic is tunneled but the response traffic\n                  is sent directly from the remote node.  In \"DSR\" mode, the remote\n                  node appears to use the IP of the ingress node; this requires a\n                  permissive L2 network.  [Default: Tunnel]'\n                type: string\n              bpfHostConntrackBypass:\n                description: 'BPFHostConntrackBypass Controls whether to bypass Linux\n                  conntrack in BPF mode for workloads and services. [Default: true\n                  - bypass Linux conntrack]'\n                type: boolean\n              bpfKubeProxyEndpointSlicesEnabled:\n                description: BPFKubeProxyEndpointSlicesEnabled in BPF mode, controls\n                  whether Felix's embedded kube-proxy accepts EndpointSlices or not.\n                type: boolean\n              bpfKubeProxyIptablesCleanupEnabled:\n                description: 'BPFKubeProxyIptablesCleanupEnabled, if enabled in BPF\n                  mode, Felix will proactively clean up the upstream Kubernetes kube-proxy''s\n                  iptables chains.  Should only be enabled if kube-proxy is not running.  [Default:\n                  true]'\n                type: boolean\n              bpfKubeProxyMinSyncPeriod:\n                description: 'BPFKubeProxyMinSyncPeriod, in BPF mode, controls the\n                  minimum time between updates to the dataplane for Felix''s embedded\n                  kube-proxy.  Lower values give reduced set-up latency.  Higher values\n                  reduce Felix CPU usage by batching up more work.  [Default: 1s]'\n                type: string\n              bpfL3IfacePattern:\n                description: BPFL3IfacePattern is a regular expression that allows\n                  to list tunnel devices like wireguard or vxlan (i.e., L3 devices)\n                  in addition to BPFDataIfacePattern. That is, tunnel interfaces not\n                  created by Calico, that Calico workload traffic flows over as well\n                  as any interfaces that handle incoming traffic to nodeports and\n                  services from outside the cluster.\n                type: string\n              bpfLogLevel:\n                description: 'BPFLogLevel controls the log level of the BPF programs\n                  when in BPF dataplane mode.  One of \"Off\", \"Info\", or \"Debug\".  The\n                  logs are emitted to the BPF trace pipe, accessible with the command\n                  `tc exec bpf debug`. [Default: Off].'\n                type: string\n              bpfMapSizeConntrack:\n                description: 'BPFMapSizeConntrack sets the size for the conntrack\n                  map.  This map must be large enough to hold an entry for each active\n                  connection.  Warning: changing the size of the conntrack map can\n                  cause disruption.'\n                type: integer\n              bpfMapSizeIPSets:\n                description: BPFMapSizeIPSets sets the size for ipsets map.  The IP\n                  sets map must be large enough to hold an entry for each endpoint\n                  matched by every selector in the source/destination matches in network\n                  policy.  Selectors such as \"all()\" can result in large numbers of\n                  entries (one entry per endpoint in that case).\n                type: integer\n              bpfMapSizeIfState:\n                description: BPFMapSizeIfState sets the size for ifstate map.  The\n                  ifstate map must be large enough to hold an entry for each device\n                  (host + workloads) on a host.\n                type: integer\n              bpfMapSizeNATAffinity:\n                type: integer\n              bpfMapSizeNATBackend:\n                description: BPFMapSizeNATBackend sets the size for nat back end map.\n                  This is the total number of endpoints. This is mostly more than\n                  the size of the number of services.\n                type: integer\n              bpfMapSizeNATFrontend:\n                description: BPFMapSizeNATFrontend sets the size for nat front end\n                  map. FrontendMap should be large enough to hold an entry for each\n                  nodeport, external IP and each port in each service.\n                type: integer\n              bpfMapSizeRoute:\n                description: BPFMapSizeRoute sets the size for the routes map.  The\n                  routes map should be large enough to hold one entry per workload\n                  and a handful of entries per host (enough to cover its own IPs and\n                  tunnel IPs).\n                type: integer\n              bpfPSNATPorts:\n                anyOf:\n                - type: integer\n                - type: string\n                description: 'BPFPSNATPorts sets the range from which we randomly\n                  pick a port if there is a source port collision. This should be\n                  within the ephemeral range as defined by RFC 6056 (1024–65535) and\n                  preferably outside the  ephemeral ranges used by common operating\n                  systems. Linux uses 32768–60999, while others mostly use the IANA\n                  defined range 49152–65535. It is not necessarily a problem if this\n                  range overlaps with the operating systems. Both ends of the range\n                  are inclusive. [Default: 20000:29999]'\n                pattern: ^.*\n                x-kubernetes-int-or-string: true\n              bpfPolicyDebugEnabled:\n                description: BPFPolicyDebugEnabled when true, Felix records detailed\n                  information about the BPF policy programs, which can be examined\n                  with the calico-bpf command-line tool.\n                type: boolean\n              chainInsertMode:\n                description: 'ChainInsertMode controls whether Felix hooks the kernel''s\n                  top-level iptables chains by inserting a rule at the top of the\n                  chain or by appending a rule at the bottom. insert is the safe default\n                  since it prevents Calico''s rules from being bypassed. If you switch\n                  to append mode, be sure that the other rules in the chains signal\n                  acceptance by falling through to the Calico rules, otherwise the\n                  Calico policy will be bypassed. [Default: insert]'\n                type: string\n              dataplaneDriver:\n                description: DataplaneDriver filename of the external dataplane driver\n                  to use.  Only used if UseInternalDataplaneDriver is set to false.\n                type: string\n              dataplaneWatchdogTimeout:\n                description: \"DataplaneWatchdogTimeout is the readiness/liveness timeout\n                  used for Felix's (internal) dataplane driver. Increase this value\n                  if you experience spurious non-ready or non-live events when Felix\n                  is under heavy load. Decrease the value to get felix to report non-live\n                  or non-ready more quickly. [Default: 90s] \\n Deprecated: replaced\n                  by the generic HealthTimeoutOverrides.\"\n                type: string\n              debugDisableLogDropping:\n                type: boolean\n              debugMemoryProfilePath:\n                type: string\n              debugSimulateCalcGraphHangAfter:\n                type: string\n              debugSimulateDataplaneHangAfter:\n                type: string\n              defaultEndpointToHostAction:\n                description: 'DefaultEndpointToHostAction controls what happens to\n                  traffic that goes from a workload endpoint to the host itself (after\n                  the traffic hits the endpoint egress policy). By default Calico\n                  blocks traffic from workload endpoints to the host itself with an\n                  iptables \"DROP\" action. If you want to allow some or all traffic\n                  from endpoint to host, set this parameter to RETURN or ACCEPT. Use\n                  RETURN if you have your own rules in the iptables \"INPUT\" chain;\n                  Calico will insert its rules at the top of that chain, then \"RETURN\"\n                  packets to the \"INPUT\" chain once it has completed processing workload\n                  endpoint egress policy. Use ACCEPT to unconditionally accept packets\n                  from workloads after processing workload endpoint egress policy.\n                  [Default: Drop]'\n                type: string\n              deviceRouteProtocol:\n                description: This defines the route protocol added to programmed device\n                  routes, by default this will be RTPROT_BOOT when left blank.\n                type: integer\n              deviceRouteSourceAddress:\n                description: This is the IPv4 source address to use on programmed\n                  device routes. By default the source address is left blank, leaving\n                  the kernel to choose the source address used.\n                type: string\n              deviceRouteSourceAddressIPv6:\n                description: This is the IPv6 source address to use on programmed\n                  device routes. By default the source address is left blank, leaving\n                  the kernel to choose the source address used.\n                type: string\n              disableConntrackInvalidCheck:\n                type: boolean\n              endpointReportingDelay:\n                type: string\n              endpointReportingEnabled:\n                type: boolean\n              externalNodesList:\n                description: ExternalNodesCIDRList is a list of CIDR's of external-non-calico-nodes\n                  which may source tunnel traffic and have the tunneled traffic be\n                  accepted at calico nodes.\n                items:\n                  type: string\n                type: array\n              failsafeInboundHostPorts:\n                description: 'FailsafeInboundHostPorts is a list of UDP/TCP ports\n                  and CIDRs that Felix will allow incoming traffic to host endpoints\n                  on irrespective of the security policy. This is useful to avoid\n                  accidentally cutting off a host with incorrect configuration. For\n                  back-compatibility, if the protocol is not specified, it defaults\n                  to \"tcp\". If a CIDR is not specified, it will allow traffic from\n                  all addresses. To disable all inbound host ports, use the value\n                  none. The default value allows ssh access and DHCP. [Default: tcp:22,\n                  udp:68, tcp:179, tcp:2379, tcp:2380, tcp:6443, tcp:6666, tcp:6667]'\n                items:\n                  description: ProtoPort is combination of protocol, port, and CIDR.\n                    Protocol and port must be specified.\n                  properties:\n                    net:\n                      type: string\n                    port:\n                      type: integer\n                    protocol:\n                      type: string\n                  required:\n                  - port\n                  - protocol\n                  type: object\n                type: array\n              failsafeOutboundHostPorts:\n                description: 'FailsafeOutboundHostPorts is a list of UDP/TCP ports\n                  and CIDRs that Felix will allow outgoing traffic from host endpoints\n                  to irrespective of the security policy. This is useful to avoid\n                  accidentally cutting off a host with incorrect configuration. For\n                  back-compatibility, if the protocol is not specified, it defaults\n                  to \"tcp\". If a CIDR is not specified, it will allow traffic from\n                  all addresses. To disable all outbound host ports, use the value\n                  none. The default value opens etcd''s standard ports to ensure that\n                  Felix does not get cut off from etcd as well as allowing DHCP and\n                  DNS. [Default: tcp:179, tcp:2379, tcp:2380, tcp:6443, tcp:6666,\n                  tcp:6667, udp:53, udp:67]'\n                items:\n                  description: ProtoPort is combination of protocol, port, and CIDR.\n                    Protocol and port must be specified.\n                  properties:\n                    net:\n                      type: string\n                    port:\n                      type: integer\n                    protocol:\n                      type: string\n                  required:\n                  - port\n                  - protocol\n                  type: object\n                type: array\n              featureDetectOverride:\n                description: FeatureDetectOverride is used to override feature detection\n                  based on auto-detected platform capabilities.  Values are specified\n                  in a comma separated list with no spaces, example; \"SNATFullyRandom=true,MASQFullyRandom=false,RestoreSupportsLock=\".  \"true\"\n                  or \"false\" will force the feature, empty or omitted values are auto-detected.\n                type: string\n              featureGates:\n                description: FeatureGates is used to enable or disable tech-preview\n                  Calico features. Values are specified in a comma separated list\n                  with no spaces, example; \"BPFConnectTimeLoadBalancingWorkaround=enabled,XyZ=false\".\n                  This is used to enable features that are not fully production ready.\n                type: string\n              floatingIPs:\n                description: FloatingIPs configures whether or not Felix will program\n                  non-OpenStack floating IP addresses.  (OpenStack-derived floating\n                  IPs are always programmed, regardless of this setting.)\n                enum:\n                - Enabled\n                - Disabled\n                type: string\n              genericXDPEnabled:\n                description: 'GenericXDPEnabled enables Generic XDP so network cards\n                  that don''t support XDP offload or driver modes can use XDP. This\n                  is not recommended since it doesn''t provide better performance\n                  than iptables. [Default: false]'\n                type: boolean\n              healthEnabled:\n                type: boolean\n              healthHost:\n                type: string\n              healthPort:\n                type: integer\n              healthTimeoutOverrides:\n                description: HealthTimeoutOverrides allows the internal watchdog timeouts\n                  of individual subcomponents to be overridden.  This is useful for\n                  working around \"false positive\" liveness timeouts that can occur\n                  in particularly stressful workloads or if CPU is constrained.  For\n                  a list of active subcomponents, see Felix's logs.\n                items:\n                  properties:\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                  required:\n                  - name\n                  - timeout\n                  type: object\n                type: array\n              interfaceExclude:\n                description: 'InterfaceExclude is a comma-separated list of interfaces\n                  that Felix should exclude when monitoring for host endpoints. The\n                  default value ensures that Felix ignores Kubernetes'' IPVS dummy\n                  interface, which is used internally by kube-proxy. If you want to\n                  exclude multiple interface names using a single value, the list\n                  supports regular expressions. For regular expressions you must wrap\n                  the value with ''/''. For example having values ''/^kube/,veth1''\n                  will exclude all interfaces that begin with ''kube'' and also the\n                  interface ''veth1''. [Default: kube-ipvs0]'\n                type: string\n              interfacePrefix:\n                description: 'InterfacePrefix is the interface name prefix that identifies\n                  workload endpoints and so distinguishes them from host endpoint\n                  interfaces. Note: in environments other than bare metal, the orchestrators\n                  configure this appropriately. For example our Kubernetes and Docker\n                  integrations set the ''cali'' value, and our OpenStack integration\n                  sets the ''tap'' value. [Default: cali]'\n                type: string\n              interfaceRefreshInterval:\n                description: InterfaceRefreshInterval is the period at which Felix\n                  rescans local interfaces to verify their state. The rescan can be\n                  disabled by setting the interval to 0.\n                type: string\n              ipipEnabled:\n                description: 'IPIPEnabled overrides whether Felix should configure\n                  an IPIP interface on the host. Optional as Felix determines this\n                  based on the existing IP pools. [Default: nil (unset)]'\n                type: boolean\n              ipipMTU:\n                description: 'IPIPMTU is the MTU to set on the tunnel device. See\n                  Configuring MTU [Default: 1440]'\n                type: integer\n              ipsetsRefreshInterval:\n                description: 'IpsetsRefreshInterval is the period at which Felix re-checks\n                  all iptables state to ensure that no other process has accidentally\n                  broken Calico''s rules. Set to 0 to disable iptables refresh. [Default:\n                  90s]'\n                type: string\n              iptablesBackend:\n                description: IptablesBackend specifies which backend of iptables will\n                  be used. The default is Auto.\n                type: string\n              iptablesFilterAllowAction:\n                type: string\n              iptablesFilterDenyAction:\n                description: IptablesFilterDenyAction controls what happens to traffic\n                  that is denied by network policy. By default Calico blocks traffic\n                  with an iptables \"DROP\" action. If you want to use \"REJECT\" action\n                  instead you can configure it in here.\n                type: string\n              iptablesLockFilePath:\n                description: 'IptablesLockFilePath is the location of the iptables\n                  lock file. You may need to change this if the lock file is not in\n                  its standard location (for example if you have mapped it into Felix''s\n                  container at a different path). [Default: /run/xtables.lock]'\n                type: string\n              iptablesLockProbeInterval:\n                description: 'IptablesLockProbeInterval is the time that Felix will\n                  wait between attempts to acquire the iptables lock if it is not\n                  available. Lower values make Felix more responsive when the lock\n                  is contended, but use more CPU. [Default: 50ms]'\n                type: string\n              iptablesLockTimeout:\n                description: 'IptablesLockTimeout is the time that Felix will wait\n                  for the iptables lock, or 0, to disable. To use this feature, Felix\n                  must share the iptables lock file with all other processes that\n                  also take the lock. When running Felix inside a container, this\n                  requires the /run directory of the host to be mounted into the calico/node\n                  or calico/felix container. [Default: 0s disabled]'\n                type: string\n              iptablesMangleAllowAction:\n                type: string\n              iptablesMarkMask:\n                description: 'IptablesMarkMask is the mask that Felix selects its\n                  IPTables Mark bits from. Should be a 32 bit hexadecimal number with\n                  at least 8 bits set, none of which clash with any other mark bits\n                  in use on the system. [Default: 0xff000000]'\n                format: int32\n                type: integer\n              iptablesNATOutgoingInterfaceFilter:\n                type: string\n              iptablesPostWriteCheckInterval:\n                description: 'IptablesPostWriteCheckInterval is the period after Felix\n                  has done a write to the dataplane that it schedules an extra read\n                  back in order to check the write was not clobbered by another process.\n                  This should only occur if another application on the system doesn''t\n                  respect the iptables lock. [Default: 1s]'\n                type: string\n              iptablesRefreshInterval:\n                description: 'IptablesRefreshInterval is the period at which Felix\n                  re-checks the IP sets in the dataplane to ensure that no other process\n                  has accidentally broken Calico''s rules. Set to 0 to disable IP\n                  sets refresh. Note: the default for this value is lower than the\n                  other refresh intervals as a workaround for a Linux kernel bug that\n                  was fixed in kernel version 4.11. If you are using v4.11 or greater\n                  you may want to set this to, a higher value to reduce Felix CPU\n                  usage. [Default: 10s]'\n                type: string\n              ipv6Support:\n                description: IPv6Support controls whether Felix enables support for\n                  IPv6 (if supported by the in-use dataplane).\n                type: boolean\n              kubeNodePortRanges:\n                description: 'KubeNodePortRanges holds list of port ranges used for\n                  service node ports. Only used if felix detects kube-proxy running\n                  in ipvs mode. Felix uses these ranges to separate host and workload\n                  traffic. [Default: 30000:32767].'\n                items:\n                  anyOf:\n                  - type: integer\n                  - type: string\n                  pattern: ^.*\n                  x-kubernetes-int-or-string: true\n                type: array\n              logDebugFilenameRegex:\n                description: LogDebugFilenameRegex controls which source code files\n                  have their Debug log output included in the logs. Only logs from\n                  files with names that match the given regular expression are included.  The\n                  filter only applies to Debug level logs.\n                type: string\n              logFilePath:\n                description: 'LogFilePath is the full path to the Felix log. Set to\n                  none to disable file logging. [Default: /var/log/calico/felix.log]'\n                type: string\n              logPrefix:\n                description: 'LogPrefix is the log prefix that Felix uses when rendering\n                  LOG rules. [Default: calico-packet]'\n                type: string\n              logSeverityFile:\n                description: 'LogSeverityFile is the log severity above which logs\n                  are sent to the log file. [Default: Info]'\n                type: string\n              logSeverityScreen:\n                description: 'LogSeverityScreen is the log severity above which logs\n                  are sent to the stdout. [Default: Info]'\n                type: string\n              logSeveritySys:\n                description: 'LogSeveritySys is the log severity above which logs\n                  are sent to the syslog. Set to None for no logging to syslog. [Default:\n                  Info]'\n                type: string\n              maxIpsetSize:\n                type: integer\n              metadataAddr:\n                description: 'MetadataAddr is the IP address or domain name of the\n                  server that can answer VM queries for cloud-init metadata. In OpenStack,\n                  this corresponds to the machine running nova-api (or in Ubuntu,\n                  nova-api-metadata). A value of none (case insensitive) means that\n                  Felix should not set up any NAT rule for the metadata path. [Default:\n                  127.0.0.1]'\n                type: string\n              metadataPort:\n                description: 'MetadataPort is the port of the metadata server. This,\n                  combined with global.MetadataAddr (if not ''None''), is used to\n                  set up a NAT rule, from 169.254.169.254:80 to MetadataAddr:MetadataPort.\n                  In most cases this should not need to be changed [Default: 8775].'\n                type: integer\n              mtuIfacePattern:\n                description: MTUIfacePattern is a regular expression that controls\n                  which interfaces Felix should scan in order to calculate the host's\n                  MTU. This should not match workload interfaces (usually named cali...).\n                type: string\n              natOutgoingAddress:\n                description: NATOutgoingAddress specifies an address to use when performing\n                  source NAT for traffic in a natOutgoing pool that is leaving the\n                  network. By default the address used is an address on the interface\n                  the traffic is leaving on (ie it uses the iptables MASQUERADE target)\n                type: string\n              natPortRange:\n                anyOf:\n                - type: integer\n                - type: string\n                description: NATPortRange specifies the range of ports that is used\n                  for port mapping when doing outgoing NAT. When unset the default\n                  behavior of the network stack is used.\n                pattern: ^.*\n                x-kubernetes-int-or-string: true\n              netlinkTimeout:\n                type: string\n              openstackRegion:\n                description: 'OpenstackRegion is the name of the region that a particular\n                  Felix belongs to. In a multi-region Calico/OpenStack deployment,\n                  this must be configured somehow for each Felix (here in the datamodel,\n                  or in felix.cfg or the environment on each compute node), and must\n                  match the [calico] openstack_region value configured in neutron.conf\n                  on each node. [Default: Empty]'\n                type: string\n              policySyncPathPrefix:\n                description: 'PolicySyncPathPrefix is used to by Felix to communicate\n                  policy changes to external services, like Application layer policy.\n                  [Default: Empty]'\n                type: string\n              prometheusGoMetricsEnabled:\n                description: 'PrometheusGoMetricsEnabled disables Go runtime metrics\n                  collection, which the Prometheus client does by default, when set\n                  to false. This reduces the number of metrics reported, reducing\n                  Prometheus load. [Default: true]'\n                type: boolean\n              prometheusMetricsEnabled:\n                description: 'PrometheusMetricsEnabled enables the Prometheus metrics\n                  server in Felix if set to true. [Default: false]'\n                type: boolean\n              prometheusMetricsHost:\n                description: 'PrometheusMetricsHost is the host that the Prometheus\n                  metrics server should bind to. [Default: empty]'\n                type: string\n              prometheusMetricsPort:\n                description: 'PrometheusMetricsPort is the TCP port that the Prometheus\n                  metrics server should bind to. [Default: 9091]'\n                type: integer\n              prometheusProcessMetricsEnabled:\n                description: 'PrometheusProcessMetricsEnabled disables process metrics\n                  collection, which the Prometheus client does by default, when set\n                  to false. This reduces the number of metrics reported, reducing\n                  Prometheus load. [Default: true]'\n                type: boolean\n              prometheusWireGuardMetricsEnabled:\n                description: 'PrometheusWireGuardMetricsEnabled disables wireguard\n                  metrics collection, which the Prometheus client does by default,\n                  when set to false. This reduces the number of metrics reported,\n                  reducing Prometheus load. [Default: true]'\n                type: boolean\n              removeExternalRoutes:\n                description: Whether or not to remove device routes that have not\n                  been programmed by Felix. Disabling this will allow external applications\n                  to also add device routes. This is enabled by default which means\n                  we will remove externally added routes.\n                type: boolean\n              reportingInterval:\n                description: 'ReportingInterval is the interval at which Felix reports\n                  its status into the datastore or 0 to disable. Must be non-zero\n                  in OpenStack deployments. [Default: 30s]'\n                type: string\n              reportingTTL:\n                description: 'ReportingTTL is the time-to-live setting for process-wide\n                  status reports. [Default: 90s]'\n                type: string\n              routeRefreshInterval:\n                description: 'RouteRefreshInterval is the period at which Felix re-checks\n                  the routes in the dataplane to ensure that no other process has\n                  accidentally broken Calico''s rules. Set to 0 to disable route refresh.\n                  [Default: 90s]'\n                type: string\n              routeSource:\n                description: 'RouteSource configures where Felix gets its routing\n                  information. - WorkloadIPs: use workload endpoints to construct\n                  routes. - CalicoIPAM: the default - use IPAM data to construct routes.'\n                type: string\n              routeSyncDisabled:\n                description: RouteSyncDisabled will disable all operations performed\n                  on the route table. Set to true to run in network-policy mode only.\n                type: boolean\n              routeTableRange:\n                description: Deprecated in favor of RouteTableRanges. Calico programs\n                  additional Linux route tables for various purposes. RouteTableRange\n                  specifies the indices of the route tables that Calico should use.\n                properties:\n                  max:\n                    type: integer\n                  min:\n                    type: integer\n                required:\n                - max\n                - min\n                type: object\n              routeTableRanges:\n                description: Calico programs additional Linux route tables for various\n                  purposes. RouteTableRanges specifies a set of table index ranges\n                  that Calico should use. Deprecates`RouteTableRange`, overrides `RouteTableRange`.\n                items:\n                  properties:\n                    max:\n                      type: integer\n                    min:\n                      type: integer\n                  required:\n                  - max\n                  - min\n                  type: object\n                type: array\n              serviceLoopPrevention:\n                description: 'When service IP advertisement is enabled, prevent routing\n                  loops to service IPs that are not in use, by dropping or rejecting\n                  packets that do not get DNAT''d by kube-proxy. Unless set to \"Disabled\",\n                  in which case such routing loops continue to be allowed. [Default:\n                  Drop]'\n                type: string\n              sidecarAccelerationEnabled:\n                description: 'SidecarAccelerationEnabled enables experimental sidecar\n                  acceleration [Default: false]'\n                type: boolean\n              usageReportingEnabled:\n                description: 'UsageReportingEnabled reports anonymous Calico version\n                  number and cluster size to projectcalico.org. Logs warnings returned\n                  by the usage server. For example, if a significant security vulnerability\n                  has been discovered in the version of Calico being used. [Default:\n                  true]'\n                type: boolean\n              usageReportingInitialDelay:\n                description: 'UsageReportingInitialDelay controls the minimum delay\n                  before Felix makes a report. [Default: 300s]'\n                type: string\n              usageReportingInterval:\n                description: 'UsageReportingInterval controls the interval at which\n                  Felix makes reports. [Default: 86400s]'\n                type: string\n              useInternalDataplaneDriver:\n                description: UseInternalDataplaneDriver, if true, Felix will use its\n                  internal dataplane programming logic.  If false, it will launch\n                  an external dataplane driver and communicate with it over protobuf.\n                type: boolean\n              vxlanEnabled:\n                description: 'VXLANEnabled overrides whether Felix should create the\n                  VXLAN tunnel device for IPv4 VXLAN networking. Optional as Felix\n                  determines this based on the existing IP pools. [Default: nil (unset)]'\n                type: boolean\n              vxlanMTU:\n                description: 'VXLANMTU is the MTU to set on the IPv4 VXLAN tunnel\n                  device. See Configuring MTU [Default: 1410]'\n                type: integer\n              vxlanMTUV6:\n                description: 'VXLANMTUV6 is the MTU to set on the IPv6 VXLAN tunnel\n                  device. See Configuring MTU [Default: 1390]'\n                type: integer\n              vxlanPort:\n                type: integer\n              vxlanVNI:\n                type: integer\n              wireguardEnabled:\n                description: 'WireguardEnabled controls whether Wireguard is enabled\n                  for IPv4 (encapsulating IPv4 traffic over an IPv4 underlay network).\n                  [Default: false]'\n                type: boolean\n              wireguardEnabledV6:\n                description: 'WireguardEnabledV6 controls whether Wireguard is enabled\n                  for IPv6 (encapsulating IPv6 traffic over an IPv6 underlay network).\n                  [Default: false]'\n                type: boolean\n              wireguardHostEncryptionEnabled:\n                description: 'WireguardHostEncryptionEnabled controls whether Wireguard\n                  host-to-host encryption is enabled. [Default: false]'\n                type: boolean\n              wireguardInterfaceName:\n                description: 'WireguardInterfaceName specifies the name to use for\n                  the IPv4 Wireguard interface. [Default: wireguard.cali]'\n                type: string\n              wireguardInterfaceNameV6:\n                description: 'WireguardInterfaceNameV6 specifies the name to use for\n                  the IPv6 Wireguard interface. [Default: wg-v6.cali]'\n                type: string\n              wireguardKeepAlive:\n                description: 'WireguardKeepAlive controls Wireguard PersistentKeepalive\n                  option. Set 0 to disable. [Default: 0]'\n                type: string\n              wireguardListeningPort:\n                description: 'WireguardListeningPort controls the listening port used\n                  by IPv4 Wireguard. [Default: 51820]'\n                type: integer\n              wireguardListeningPortV6:\n                description: 'WireguardListeningPortV6 controls the listening port\n                  used by IPv6 Wireguard. [Default: 51821]'\n                type: integer\n              wireguardMTU:\n                description: 'WireguardMTU controls the MTU on the IPv4 Wireguard\n                  interface. See Configuring MTU [Default: 1440]'\n                type: integer\n              wireguardMTUV6:\n                description: 'WireguardMTUV6 controls the MTU on the IPv6 Wireguard\n                  interface. See Configuring MTU [Default: 1420]'\n                type: integer\n              wireguardRoutingRulePriority:\n                description: 'WireguardRoutingRulePriority controls the priority value\n                  to use for the Wireguard routing rule. [Default: 99]'\n                type: integer\n              workloadSourceSpoofing:\n                description: WorkloadSourceSpoofing controls whether pods can use\n                  the allowedSourcePrefixes annotation to send traffic with a source\n                  IP address that is not theirs. This is disabled by default. When\n                  set to \"Any\", pods can request any prefix.\n                type: string\n              xdpEnabled:\n                description: 'XDPEnabled enables XDP acceleration for suitable untracked\n                  incoming deny rules. [Default: true]'\n                type: boolean\n              xdpRefreshInterval:\n                description: 'XDPRefreshInterval is the period at which Felix re-checks\n                  all XDP state to ensure that no other process has accidentally broken\n                  Calico''s BPF maps or attached programs. Set to 0 to disable XDP\n                  refresh. [Default: 90s]'\n                type: string\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: globalnetworkpolicies.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: GlobalNetworkPolicy\n    listKind: GlobalNetworkPolicyList\n    plural: globalnetworkpolicies\n    singular: globalnetworkpolicy\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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            properties:\n              applyOnForward:\n                description: ApplyOnForward indicates to apply the rules in this policy\n                  on forward traffic.\n                type: boolean\n              doNotTrack:\n                description: DoNotTrack indicates whether packets matched by the rules\n                  in this policy should go through the data plane's connection tracking,\n                  such as Linux conntrack.  If True, the rules in this policy are\n                  applied before any data plane connection tracking, and packets allowed\n                  by this policy are marked as not to be tracked.\n                type: boolean\n              egress:\n                description: The ordered set of egress rules.  Each rule contains\n                  a set of packet match criteria and a corresponding action to apply.\n                items:\n                  description: \"A Rule encapsulates a set of match criteria and an\n                    action.  Both selector-based security Policy and security Profiles\n                    reference rules - separated out as a list of rules for both ingress\n                    and egress packet matching. \\n Each positive match criteria has\n                    a negated version, prefixed with \\\"Not\\\". All the match criteria\n                    within a rule must be satisfied for a packet to match. A single\n                    rule can contain the positive and negative version of a match\n                    and both must be satisfied for the rule to match.\"\n                  properties:\n                    action:\n                      type: string\n                    destination:\n                      description: Destination contains the match criteria that apply\n                        to destination entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                    http:\n                      description: HTTP contains match criteria that apply to HTTP\n                        requests.\n                      properties:\n                        methods:\n                          description: Methods is an optional field that restricts\n                            the rule to apply only to HTTP requests that use one of\n                            the listed HTTP Methods (e.g. GET, PUT, etc.) Multiple\n                            methods are OR'd together.\n                          items:\n                            type: string\n                          type: array\n                        paths:\n                          description: 'Paths is an optional field that restricts\n                            the rule to apply to HTTP requests that use one of the\n                            listed HTTP Paths. Multiple paths are OR''d together.\n                            e.g: - exact: /foo - prefix: /bar NOTE: Each entry may\n                            ONLY specify either a `exact` or a `prefix` match. The\n                            validator will check for it.'\n                          items:\n                            description: 'HTTPPath specifies an HTTP path to match.\n                              It may be either of the form: exact: <path>: which matches\n                              the path exactly or prefix: <path-prefix>: which matches\n                              the path prefix'\n                            properties:\n                              exact:\n                                type: string\n                              prefix:\n                                type: string\n                            type: object\n                          type: array\n                      type: object\n                    icmp:\n                      description: ICMP is an optional field that restricts the rule\n                        to apply to a specific type and code of ICMP traffic.  This\n                        should only be specified if the Protocol field is set to \"ICMP\"\n                        or \"ICMPv6\".\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    ipVersion:\n                      description: IPVersion is an optional field that restricts the\n                        rule to only match a specific IP version.\n                      type: integer\n                    metadata:\n                      description: Metadata contains additional information for this\n                        rule\n                      properties:\n                        annotations:\n                          additionalProperties:\n                            type: string\n                          description: Annotations is a set of key value pairs that\n                            give extra information about the rule\n                          type: object\n                      type: object\n                    notICMP:\n                      description: NotICMP is the negated version of the ICMP field.\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    notProtocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: NotProtocol is the negated version of the Protocol\n                        field.\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    protocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: \"Protocol is an optional field that restricts the\n                        rule to only apply to traffic of a specific IP protocol. Required\n                        if any of the EntityRules contain Ports (because ports only\n                        apply to certain protocols). \\n Must be one of these string\n                        values: \\\"TCP\\\", \\\"UDP\\\", \\\"ICMP\\\", \\\"ICMPv6\\\", \\\"SCTP\\\",\n                        \\\"UDPLite\\\" or an integer in the range 1-255.\"\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    source:\n                      description: Source contains the match criteria that apply to\n                        source entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                  required:\n                  - action\n                  type: object\n                type: array\n              ingress:\n                description: The ordered set of ingress rules.  Each rule contains\n                  a set of packet match criteria and a corresponding action to apply.\n                items:\n                  description: \"A Rule encapsulates a set of match criteria and an\n                    action.  Both selector-based security Policy and security Profiles\n                    reference rules - separated out as a list of rules for both ingress\n                    and egress packet matching. \\n Each positive match criteria has\n                    a negated version, prefixed with \\\"Not\\\". All the match criteria\n                    within a rule must be satisfied for a packet to match. A single\n                    rule can contain the positive and negative version of a match\n                    and both must be satisfied for the rule to match.\"\n                  properties:\n                    action:\n                      type: string\n                    destination:\n                      description: Destination contains the match criteria that apply\n                        to destination entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                    http:\n                      description: HTTP contains match criteria that apply to HTTP\n                        requests.\n                      properties:\n                        methods:\n                          description: Methods is an optional field that restricts\n                            the rule to apply only to HTTP requests that use one of\n                            the listed HTTP Methods (e.g. GET, PUT, etc.) Multiple\n                            methods are OR'd together.\n                          items:\n                            type: string\n                          type: array\n                        paths:\n                          description: 'Paths is an optional field that restricts\n                            the rule to apply to HTTP requests that use one of the\n                            listed HTTP Paths. Multiple paths are OR''d together.\n                            e.g: - exact: /foo - prefix: /bar NOTE: Each entry may\n                            ONLY specify either a `exact` or a `prefix` match. The\n                            validator will check for it.'\n                          items:\n                            description: 'HTTPPath specifies an HTTP path to match.\n                              It may be either of the form: exact: <path>: which matches\n                              the path exactly or prefix: <path-prefix>: which matches\n                              the path prefix'\n                            properties:\n                              exact:\n                                type: string\n                              prefix:\n                                type: string\n                            type: object\n                          type: array\n                      type: object\n                    icmp:\n                      description: ICMP is an optional field that restricts the rule\n                        to apply to a specific type and code of ICMP traffic.  This\n                        should only be specified if the Protocol field is set to \"ICMP\"\n                        or \"ICMPv6\".\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    ipVersion:\n                      description: IPVersion is an optional field that restricts the\n                        rule to only match a specific IP version.\n                      type: integer\n                    metadata:\n                      description: Metadata contains additional information for this\n                        rule\n                      properties:\n                        annotations:\n                          additionalProperties:\n                            type: string\n                          description: Annotations is a set of key value pairs that\n                            give extra information about the rule\n                          type: object\n                      type: object\n                    notICMP:\n                      description: NotICMP is the negated version of the ICMP field.\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    notProtocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: NotProtocol is the negated version of the Protocol\n                        field.\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    protocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: \"Protocol is an optional field that restricts the\n                        rule to only apply to traffic of a specific IP protocol. Required\n                        if any of the EntityRules contain Ports (because ports only\n                        apply to certain protocols). \\n Must be one of these string\n                        values: \\\"TCP\\\", \\\"UDP\\\", \\\"ICMP\\\", \\\"ICMPv6\\\", \\\"SCTP\\\",\n                        \\\"UDPLite\\\" or an integer in the range 1-255.\"\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    source:\n                      description: Source contains the match criteria that apply to\n                        source entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                  required:\n                  - action\n                  type: object\n                type: array\n              namespaceSelector:\n                description: NamespaceSelector is an optional field for an expression\n                  used to select a pod based on namespaces.\n                type: string\n              order:\n                description: Order is an optional field that specifies the order in\n                  which the policy is applied. Policies with higher \"order\" are applied\n                  after those with lower order.  If the order is omitted, it may be\n                  considered to be \"infinite\" - i.e. the policy will be applied last.  Policies\n                  with identical order will be applied in alphanumerical order based\n                  on the Policy \"Name\".\n                type: number\n              preDNAT:\n                description: PreDNAT indicates to apply the rules in this policy before\n                  any DNAT.\n                type: boolean\n              selector:\n                description: \"The selector is an expression used to pick pick out\n                  the endpoints that the policy should be applied to. \\n Selector\n                  expressions follow this syntax: \\n \\tlabel == \\\"string_literal\\\"\n                  \\ ->  comparison, e.g. my_label == \\\"foo bar\\\" \\tlabel != \\\"string_literal\\\"\n                  \\  ->  not equal; also matches if label is not present \\tlabel in\n                  { \\\"a\\\", \\\"b\\\", \\\"c\\\", ... }  ->  true if the value of label X is\n                  one of \\\"a\\\", \\\"b\\\", \\\"c\\\" \\tlabel not in { \\\"a\\\", \\\"b\\\", \\\"c\\\",\n                  ... }  ->  true if the value of label X is not one of \\\"a\\\", \\\"b\\\",\n                  \\\"c\\\" \\thas(label_name)  -> True if that label is present \\t! expr\n                  -> negation of expr \\texpr && expr  -> Short-circuit and \\texpr\n                  || expr  -> Short-circuit or \\t( expr ) -> parens for grouping \\tall()\n                  or the empty selector -> matches all endpoints. \\n Label names are\n                  allowed to contain alphanumerics, -, _ and /. String literals are\n                  more permissive but they do not support escape characters. \\n Examples\n                  (with made-up labels): \\n \\ttype == \\\"webserver\\\" && deployment\n                  == \\\"prod\\\" \\ttype in {\\\"frontend\\\", \\\"backend\\\"} \\tdeployment !=\n                  \\\"dev\\\" \\t! has(label_name)\"\n                type: string\n              serviceAccountSelector:\n                description: ServiceAccountSelector is an optional field for an expression\n                  used to select a pod based on service accounts.\n                type: string\n              types:\n                description: \"Types indicates whether this policy applies to ingress,\n                  or to egress, or to both.  When not explicitly specified (and so\n                  the value on creation is empty or nil), Calico defaults Types according\n                  to what Ingress and Egress rules are present in the policy.  The\n                  default is: \\n - [ PolicyTypeIngress ], if there are no Egress rules\n                  (including the case where there are   also no Ingress rules) \\n\n                  - [ PolicyTypeEgress ], if there are Egress rules but no Ingress\n                  rules \\n - [ PolicyTypeIngress, PolicyTypeEgress ], if there are\n                  both Ingress and Egress rules. \\n When the policy is read back again,\n                  Types will always be one of these values, never empty or nil.\"\n                items:\n                  description: PolicyType enumerates the possible values of the PolicySpec\n                    Types field.\n                  type: string\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: globalnetworksets.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: GlobalNetworkSet\n    listKind: GlobalNetworkSetList\n    plural: globalnetworksets\n    singular: globalnetworkset\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: GlobalNetworkSet contains a set of arbitrary IP sub-networks/CIDRs\n          that share labels to allow rules to refer to them via selectors.  The labels\n          of GlobalNetworkSet are not namespaced.\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: GlobalNetworkSetSpec contains the specification for a NetworkSet\n              resource.\n            properties:\n              nets:\n                description: The list of IP networks that belong to this set.\n                items:\n                  type: string\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: hostendpoints.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: HostEndpoint\n    listKind: HostEndpointList\n    plural: hostendpoints\n    singular: hostendpoint\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: HostEndpointSpec contains the specification for a HostEndpoint\n              resource.\n            properties:\n              expectedIPs:\n                description: \"The expected IP addresses (IPv4 and IPv6) of the endpoint.\n                  If \\\"InterfaceName\\\" is not present, Calico will look for an interface\n                  matching any of the IPs in the list and apply policy to that. Note:\n                  \\tWhen using the selector match criteria in an ingress or egress\n                  security Policy \\tor Profile, Calico converts the selector into\n                  a set of IP addresses. For host \\tendpoints, the ExpectedIPs field\n                  is used for that purpose. (If only the interface \\tname is specified,\n                  Calico does not learn the IPs of the interface for use in match\n                  \\tcriteria.)\"\n                items:\n                  type: string\n                type: array\n              interfaceName:\n                description: \"Either \\\"*\\\", or the name of a specific Linux interface\n                  to apply policy to; or empty.  \\\"*\\\" indicates that this HostEndpoint\n                  governs all traffic to, from or through the default network namespace\n                  of the host named by the \\\"Node\\\" field; entering and leaving that\n                  namespace via any interface, including those from/to non-host-networked\n                  local workloads. \\n If InterfaceName is not \\\"*\\\", this HostEndpoint\n                  only governs traffic that enters or leaves the host through the\n                  specific interface named by InterfaceName, or - when InterfaceName\n                  is empty - through the specific interface that has one of the IPs\n                  in ExpectedIPs. Therefore, when InterfaceName is empty, at least\n                  one expected IP must be specified.  Only external interfaces (such\n                  as \\\"eth0\\\") are supported here; it isn't possible for a HostEndpoint\n                  to protect traffic through a specific local workload interface.\n                  \\n Note: Only some kinds of policy are implemented for \\\"*\\\" HostEndpoints;\n                  initially just pre-DNAT policy.  Please check Calico documentation\n                  for the latest position.\"\n                type: string\n              node:\n                description: The node name identifying the Calico node instance.\n                type: string\n              ports:\n                description: Ports contains the endpoint's named ports, which may\n                  be referenced in security policy rules.\n                items:\n                  properties:\n                    name:\n                      type: string\n                    port:\n                      type: integer\n                    protocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                  required:\n                  - name\n                  - port\n                  - protocol\n                  type: object\n                type: array\n              profiles:\n                description: A list of identifiers of security Profile objects that\n                  apply to this endpoint. Each profile is applied in the order that\n                  they appear in this list.  Profile rules are applied after the selector-based\n                  security policy.\n                items:\n                  type: string\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: ipamblocks.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: IPAMBlock\n    listKind: IPAMBlockList\n    plural: ipamblocks\n    singular: ipamblock\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: IPAMBlockSpec contains the specification for an IPAMBlock\n              resource.\n            properties:\n              affinity:\n                description: Affinity of the block, if this block has one. If set,\n                  it will be of the form \"host:<hostname>\". If not set, this block\n                  is not affine to a host.\n                type: string\n              allocations:\n                description: Array of allocations in-use within this block. nil entries\n                  mean the allocation is free. For non-nil entries at index i, the\n                  index is the ordinal of the allocation within this block and the\n                  value is the index of the associated attributes in the Attributes\n                  array.\n                items:\n                  type: integer\n                  # TODO: This nullable is manually added in. We should update controller-gen\n                  # to handle []*int properly itself.\n                  nullable: true\n                type: array\n              attributes:\n                description: Attributes is an array of arbitrary metadata associated\n                  with allocations in the block. To find attributes for a given allocation,\n                  use the value of the allocation's entry in the Allocations array\n                  as the index of the element in this array.\n                items:\n                  properties:\n                    handle_id:\n                      type: string\n                    secondary:\n                      additionalProperties:\n                        type: string\n                      type: object\n                  type: object\n                type: array\n              cidr:\n                description: The block's CIDR.\n                type: string\n              deleted:\n                description: Deleted is an internal boolean used to workaround a limitation\n                  in the Kubernetes API whereby deletion will not return a conflict\n                  error if the block has been updated. It should not be set manually.\n                type: boolean\n              sequenceNumber:\n                default: 0\n                description: We store a sequence number that is updated each time\n                  the block is written. Each allocation will also store the sequence\n                  number of the block at the time of its creation. When releasing\n                  an IP, passing the sequence number associated with the allocation\n                  allows us to protect against a race condition and ensure the IP\n                  hasn't been released and re-allocated since the release request.\n                format: int64\n                type: integer\n              sequenceNumberForAllocation:\n                additionalProperties:\n                  format: int64\n                  type: integer\n                description: Map of allocated ordinal within the block to sequence\n                  number of the block at the time of allocation. Kubernetes does not\n                  allow numerical keys for maps, so the key is cast to a string.\n                type: object\n              strictAffinity:\n                description: StrictAffinity on the IPAMBlock is deprecated and no\n                  longer used by the code. Use IPAMConfig StrictAffinity instead.\n                type: boolean\n              unallocated:\n                description: Unallocated is an ordered list of allocations which are\n                  free in the block.\n                items:\n                  type: integer\n                type: array\n            required:\n            - allocations\n            - attributes\n            - cidr\n            - strictAffinity\n            - unallocated\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: ipamconfigs.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: IPAMConfig\n    listKind: IPAMConfigList\n    plural: ipamconfigs\n    singular: ipamconfig\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: IPAMConfigSpec contains the specification for an IPAMConfig\n              resource.\n            properties:\n              autoAllocateBlocks:\n                type: boolean\n              maxBlocksPerHost:\n                description: MaxBlocksPerHost, if non-zero, is the max number of blocks\n                  that can be affine to each host.\n                maximum: 2147483647\n                minimum: 0\n                type: integer\n              strictAffinity:\n                type: boolean\n            required:\n            - autoAllocateBlocks\n            - strictAffinity\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: ipamhandles.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: IPAMHandle\n    listKind: IPAMHandleList\n    plural: ipamhandles\n    singular: ipamhandle\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: IPAMHandleSpec contains the specification for an IPAMHandle\n              resource.\n            properties:\n              block:\n                additionalProperties:\n                  type: integer\n                type: object\n              deleted:\n                type: boolean\n              handleID:\n                type: string\n            required:\n            - block\n            - handleID\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: ippools.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: IPPool\n    listKind: IPPoolList\n    plural: ippools\n    singular: ippool\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: IPPoolSpec contains the specification for an IPPool resource.\n            properties:\n              allowedUses:\n                description: AllowedUse controls what the IP pool will be used for.  If\n                  not specified or empty, defaults to [\"Tunnel\", \"Workload\"] for back-compatibility\n                items:\n                  type: string\n                type: array\n              blockSize:\n                description: The block size to use for IP address assignments from\n                  this pool. Defaults to 26 for IPv4 and 122 for IPv6.\n                type: integer\n              cidr:\n                description: The pool CIDR.\n                type: string\n              disableBGPExport:\n                description: 'Disable exporting routes from this IP Pool''s CIDR over\n                  BGP. [Default: false]'\n                type: boolean\n              disabled:\n                description: When disabled is true, Calico IPAM will not assign addresses\n                  from this pool.\n                type: boolean\n              ipip:\n                description: 'Deprecated: this field is only used for APIv1 backwards\n                  compatibility. Setting this field is not allowed, this field is\n                  for internal use only.'\n                properties:\n                  enabled:\n                    description: When enabled is true, ipip tunneling will be used\n                      to deliver packets to destinations within this pool.\n                    type: boolean\n                  mode:\n                    description: The IPIP mode.  This can be one of \"always\" or \"cross-subnet\".  A\n                      mode of \"always\" will also use IPIP tunneling for routing to\n                      destination IP addresses within this pool.  A mode of \"cross-subnet\"\n                      will only use IPIP tunneling when the destination node is on\n                      a different subnet to the originating node.  The default value\n                      (if not specified) is \"always\".\n                    type: string\n                type: object\n              ipipMode:\n                description: Contains configuration for IPIP tunneling for this pool.\n                  If not specified, then this is defaulted to \"Never\" (i.e. IPIP tunneling\n                  is disabled).\n                type: string\n              nat-outgoing:\n                description: 'Deprecated: this field is only used for APIv1 backwards\n                  compatibility. Setting this field is not allowed, this field is\n                  for internal use only.'\n                type: boolean\n              natOutgoing:\n                description: When natOutgoing is true, packets sent from Calico networked\n                  containers in this pool to destinations outside of this pool will\n                  be masqueraded.\n                type: boolean\n              nodeSelector:\n                description: Allows IPPool to allocate for a specific node by label\n                  selector.\n                type: string\n              vxlanMode:\n                description: Contains configuration for VXLAN tunneling for this pool.\n                  If not specified, then this is defaulted to \"Never\" (i.e. VXLAN\n                  tunneling is disabled).\n                type: string\n            required:\n            - cidr\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: (devel)\n  creationTimestamp: null\n  name: ipreservations.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: IPReservation\n    listKind: IPReservationList\n    plural: ipreservations\n    singular: ipreservation\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: IPReservationSpec contains the specification for an IPReservation\n              resource.\n            properties:\n              reservedCIDRs:\n                description: ReservedCIDRs is a list of CIDRs and/or IP addresses\n                  that Calico IPAM will exclude from new allocations.\n                items:\n                  type: string\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: kubecontrollersconfigurations.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: KubeControllersConfiguration\n    listKind: KubeControllersConfigurationList\n    plural: kubecontrollersconfigurations\n    singular: kubecontrollersconfiguration\n  preserveUnknownFields: false\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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: KubeControllersConfigurationSpec contains the values of the\n              Kubernetes controllers configuration.\n            properties:\n              controllers:\n                description: Controllers enables and configures individual Kubernetes\n                  controllers\n                properties:\n                  namespace:\n                    description: Namespace enables and configures the namespace controller.\n                      Enabled by default, set to nil to disable.\n                    properties:\n                      reconcilerPeriod:\n                        description: 'ReconcilerPeriod is the period to perform reconciliation\n                          with the Calico datastore. [Default: 5m]'\n                        type: string\n                    type: object\n                  node:\n                    description: Node enables and configures the node controller.\n                      Enabled by default, set to nil to disable.\n                    properties:\n                      hostEndpoint:\n                        description: HostEndpoint controls syncing nodes to host endpoints.\n                          Disabled by default, set to nil to disable.\n                        properties:\n                          autoCreate:\n                            description: 'AutoCreate enables automatic creation of\n                              host endpoints for every node. [Default: Disabled]'\n                            type: string\n                        type: object\n                      leakGracePeriod:\n                        description: 'LeakGracePeriod is the period used by the controller\n                          to determine if an IP address has been leaked. Set to 0\n                          to disable IP garbage collection. [Default: 15m]'\n                        type: string\n                      reconcilerPeriod:\n                        description: 'ReconcilerPeriod is the period to perform reconciliation\n                          with the Calico datastore. [Default: 5m]'\n                        type: string\n                      syncLabels:\n                        description: 'SyncLabels controls whether to copy Kubernetes\n                          node labels to Calico nodes. [Default: Enabled]'\n                        type: string\n                    type: object\n                  policy:\n                    description: Policy enables and configures the policy controller.\n                      Enabled by default, set to nil to disable.\n                    properties:\n                      reconcilerPeriod:\n                        description: 'ReconcilerPeriod is the period to perform reconciliation\n                          with the Calico datastore. [Default: 5m]'\n                        type: string\n                    type: object\n                  serviceAccount:\n                    description: ServiceAccount enables and configures the service\n                      account controller. Enabled by default, set to nil to disable.\n                    properties:\n                      reconcilerPeriod:\n                        description: 'ReconcilerPeriod is the period to perform reconciliation\n                          with the Calico datastore. [Default: 5m]'\n                        type: string\n                    type: object\n                  workloadEndpoint:\n                    description: WorkloadEndpoint enables and configures the workload\n                      endpoint controller. Enabled by default, set to nil to disable.\n                    properties:\n                      reconcilerPeriod:\n                        description: 'ReconcilerPeriod is the period to perform reconciliation\n                          with the Calico datastore. [Default: 5m]'\n                        type: string\n                    type: object\n                type: object\n              debugProfilePort:\n                description: DebugProfilePort configures the port to serve memory\n                  and cpu profiles on. If not specified, profiling is disabled.\n                format: int32\n                type: integer\n              etcdV3CompactionPeriod:\n                description: 'EtcdV3CompactionPeriod is the period between etcdv3\n                  compaction requests. Set to 0 to disable. [Default: 10m]'\n                type: string\n              healthChecks:\n                description: 'HealthChecks enables or disables support for health\n                  checks [Default: Enabled]'\n                type: string\n              logSeverityScreen:\n                description: 'LogSeverityScreen is the log severity above which logs\n                  are sent to the stdout. [Default: Info]'\n                type: string\n              prometheusMetricsPort:\n                description: 'PrometheusMetricsPort is the TCP port that the Prometheus\n                  metrics server should bind to. Set to 0 to disable. [Default: 9094]'\n                type: integer\n            required:\n            - controllers\n            type: object\n          status:\n            description: KubeControllersConfigurationStatus represents the status\n              of the configuration. It's useful for admins to be able to see the actual\n              config that was applied, which can be modified by environment variables\n              on the kube-controllers process.\n            properties:\n              environmentVars:\n                additionalProperties:\n                  type: string\n                description: EnvironmentVars contains the environment variables on\n                  the kube-controllers that influenced the RunningConfig.\n                type: object\n              runningConfig:\n                description: RunningConfig contains the effective config that is running\n                  in the kube-controllers pod, after merging the API resource with\n                  any environment variables.\n                properties:\n                  controllers:\n                    description: Controllers enables and configures individual Kubernetes\n                      controllers\n                    properties:\n                      namespace:\n                        description: Namespace enables and configures the namespace\n                          controller. Enabled by default, set to nil to disable.\n                        properties:\n                          reconcilerPeriod:\n                            description: 'ReconcilerPeriod is the period to perform\n                              reconciliation with the Calico datastore. [Default:\n                              5m]'\n                            type: string\n                        type: object\n                      node:\n                        description: Node enables and configures the node controller.\n                          Enabled by default, set to nil to disable.\n                        properties:\n                          hostEndpoint:\n                            description: HostEndpoint controls syncing nodes to host\n                              endpoints. Disabled by default, set to nil to disable.\n                            properties:\n                              autoCreate:\n                                description: 'AutoCreate enables automatic creation\n                                  of host endpoints for every node. [Default: Disabled]'\n                                type: string\n                            type: object\n                          leakGracePeriod:\n                            description: 'LeakGracePeriod is the period used by the\n                              controller to determine if an IP address has been leaked.\n                              Set to 0 to disable IP garbage collection. [Default:\n                              15m]'\n                            type: string\n                          reconcilerPeriod:\n                            description: 'ReconcilerPeriod is the period to perform\n                              reconciliation with the Calico datastore. [Default:\n                              5m]'\n                            type: string\n                          syncLabels:\n                            description: 'SyncLabels controls whether to copy Kubernetes\n                              node labels to Calico nodes. [Default: Enabled]'\n                            type: string\n                        type: object\n                      policy:\n                        description: Policy enables and configures the policy controller.\n                          Enabled by default, set to nil to disable.\n                        properties:\n                          reconcilerPeriod:\n                            description: 'ReconcilerPeriod is the period to perform\n                              reconciliation with the Calico datastore. [Default:\n                              5m]'\n                            type: string\n                        type: object\n                      serviceAccount:\n                        description: ServiceAccount enables and configures the service\n                          account controller. Enabled by default, set to nil to disable.\n                        properties:\n                          reconcilerPeriod:\n                            description: 'ReconcilerPeriod is the period to perform\n                              reconciliation with the Calico datastore. [Default:\n                              5m]'\n                            type: string\n                        type: object\n                      workloadEndpoint:\n                        description: WorkloadEndpoint enables and configures the workload\n                          endpoint controller. Enabled by default, set to nil to disable.\n                        properties:\n                          reconcilerPeriod:\n                            description: 'ReconcilerPeriod is the period to perform\n                              reconciliation with the Calico datastore. [Default:\n                              5m]'\n                            type: string\n                        type: object\n                    type: object\n                  debugProfilePort:\n                    description: DebugProfilePort configures the port to serve memory\n                      and cpu profiles on. If not specified, profiling is disabled.\n                    format: int32\n                    type: integer\n                  etcdV3CompactionPeriod:\n                    description: 'EtcdV3CompactionPeriod is the period between etcdv3\n                      compaction requests. Set to 0 to disable. [Default: 10m]'\n                    type: string\n                  healthChecks:\n                    description: 'HealthChecks enables or disables support for health\n                      checks [Default: Enabled]'\n                    type: string\n                  logSeverityScreen:\n                    description: 'LogSeverityScreen is the log severity above which\n                      logs are sent to the stdout. [Default: Info]'\n                    type: string\n                  prometheusMetricsPort:\n                    description: 'PrometheusMetricsPort is the TCP port that the Prometheus\n                      metrics server should bind to. Set to 0 to disable. [Default:\n                      9094]'\n                    type: integer\n                required:\n                - controllers\n                type: object\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkpolicies.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: NetworkPolicy\n    listKind: NetworkPolicyList\n    plural: networkpolicies\n    singular: networkpolicy\n  preserveUnknownFields: false\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\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            properties:\n              egress:\n                description: The ordered set of egress rules.  Each rule contains\n                  a set of packet match criteria and a corresponding action to apply.\n                items:\n                  description: \"A Rule encapsulates a set of match criteria and an\n                    action.  Both selector-based security Policy and security Profiles\n                    reference rules - separated out as a list of rules for both ingress\n                    and egress packet matching. \\n Each positive match criteria has\n                    a negated version, prefixed with \\\"Not\\\". All the match criteria\n                    within a rule must be satisfied for a packet to match. A single\n                    rule can contain the positive and negative version of a match\n                    and both must be satisfied for the rule to match.\"\n                  properties:\n                    action:\n                      type: string\n                    destination:\n                      description: Destination contains the match criteria that apply\n                        to destination entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                    http:\n                      description: HTTP contains match criteria that apply to HTTP\n                        requests.\n                      properties:\n                        methods:\n                          description: Methods is an optional field that restricts\n                            the rule to apply only to HTTP requests that use one of\n                            the listed HTTP Methods (e.g. GET, PUT, etc.) Multiple\n                            methods are OR'd together.\n                          items:\n                            type: string\n                          type: array\n                        paths:\n                          description: 'Paths is an optional field that restricts\n                            the rule to apply to HTTP requests that use one of the\n                            listed HTTP Paths. Multiple paths are OR''d together.\n                            e.g: - exact: /foo - prefix: /bar NOTE: Each entry may\n                            ONLY specify either a `exact` or a `prefix` match. The\n                            validator will check for it.'\n                          items:\n                            description: 'HTTPPath specifies an HTTP path to match.\n                              It may be either of the form: exact: <path>: which matches\n                              the path exactly or prefix: <path-prefix>: which matches\n                              the path prefix'\n                            properties:\n                              exact:\n                                type: string\n                              prefix:\n                                type: string\n                            type: object\n                          type: array\n                      type: object\n                    icmp:\n                      description: ICMP is an optional field that restricts the rule\n                        to apply to a specific type and code of ICMP traffic.  This\n                        should only be specified if the Protocol field is set to \"ICMP\"\n                        or \"ICMPv6\".\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    ipVersion:\n                      description: IPVersion is an optional field that restricts the\n                        rule to only match a specific IP version.\n                      type: integer\n                    metadata:\n                      description: Metadata contains additional information for this\n                        rule\n                      properties:\n                        annotations:\n                          additionalProperties:\n                            type: string\n                          description: Annotations is a set of key value pairs that\n                            give extra information about the rule\n                          type: object\n                      type: object\n                    notICMP:\n                      description: NotICMP is the negated version of the ICMP field.\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    notProtocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: NotProtocol is the negated version of the Protocol\n                        field.\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    protocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: \"Protocol is an optional field that restricts the\n                        rule to only apply to traffic of a specific IP protocol. Required\n                        if any of the EntityRules contain Ports (because ports only\n                        apply to certain protocols). \\n Must be one of these string\n                        values: \\\"TCP\\\", \\\"UDP\\\", \\\"ICMP\\\", \\\"ICMPv6\\\", \\\"SCTP\\\",\n                        \\\"UDPLite\\\" or an integer in the range 1-255.\"\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    source:\n                      description: Source contains the match criteria that apply to\n                        source entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                  required:\n                  - action\n                  type: object\n                type: array\n              ingress:\n                description: The ordered set of ingress rules.  Each rule contains\n                  a set of packet match criteria and a corresponding action to apply.\n                items:\n                  description: \"A Rule encapsulates a set of match criteria and an\n                    action.  Both selector-based security Policy and security Profiles\n                    reference rules - separated out as a list of rules for both ingress\n                    and egress packet matching. \\n Each positive match criteria has\n                    a negated version, prefixed with \\\"Not\\\". All the match criteria\n                    within a rule must be satisfied for a packet to match. A single\n                    rule can contain the positive and negative version of a match\n                    and both must be satisfied for the rule to match.\"\n                  properties:\n                    action:\n                      type: string\n                    destination:\n                      description: Destination contains the match criteria that apply\n                        to destination entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                    http:\n                      description: HTTP contains match criteria that apply to HTTP\n                        requests.\n                      properties:\n                        methods:\n                          description: Methods is an optional field that restricts\n                            the rule to apply only to HTTP requests that use one of\n                            the listed HTTP Methods (e.g. GET, PUT, etc.) Multiple\n                            methods are OR'd together.\n                          items:\n                            type: string\n                          type: array\n                        paths:\n                          description: 'Paths is an optional field that restricts\n                            the rule to apply to HTTP requests that use one of the\n                            listed HTTP Paths. Multiple paths are OR''d together.\n                            e.g: - exact: /foo - prefix: /bar NOTE: Each entry may\n                            ONLY specify either a `exact` or a `prefix` match. The\n                            validator will check for it.'\n                          items:\n                            description: 'HTTPPath specifies an HTTP path to match.\n                              It may be either of the form: exact: <path>: which matches\n                              the path exactly or prefix: <path-prefix>: which matches\n                              the path prefix'\n                            properties:\n                              exact:\n                                type: string\n                              prefix:\n                                type: string\n                            type: object\n                          type: array\n                      type: object\n                    icmp:\n                      description: ICMP is an optional field that restricts the rule\n                        to apply to a specific type and code of ICMP traffic.  This\n                        should only be specified if the Protocol field is set to \"ICMP\"\n                        or \"ICMPv6\".\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    ipVersion:\n                      description: IPVersion is an optional field that restricts the\n                        rule to only match a specific IP version.\n                      type: integer\n                    metadata:\n                      description: Metadata contains additional information for this\n                        rule\n                      properties:\n                        annotations:\n                          additionalProperties:\n                            type: string\n                          description: Annotations is a set of key value pairs that\n                            give extra information about the rule\n                          type: object\n                      type: object\n                    notICMP:\n                      description: NotICMP is the negated version of the ICMP field.\n                      properties:\n                        code:\n                          description: Match on a specific ICMP code.  If specified,\n                            the Type value must also be specified. This is a technical\n                            limitation imposed by the kernel's iptables firewall,\n                            which Calico uses to enforce the rule.\n                          type: integer\n                        type:\n                          description: Match on a specific ICMP type.  For example\n                            a value of 8 refers to ICMP Echo Request (i.e. pings).\n                          type: integer\n                      type: object\n                    notProtocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: NotProtocol is the negated version of the Protocol\n                        field.\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    protocol:\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      description: \"Protocol is an optional field that restricts the\n                        rule to only apply to traffic of a specific IP protocol. Required\n                        if any of the EntityRules contain Ports (because ports only\n                        apply to certain protocols). \\n Must be one of these string\n                        values: \\\"TCP\\\", \\\"UDP\\\", \\\"ICMP\\\", \\\"ICMPv6\\\", \\\"SCTP\\\",\n                        \\\"UDPLite\\\" or an integer in the range 1-255.\"\n                      pattern: ^.*\n                      x-kubernetes-int-or-string: true\n                    source:\n                      description: Source contains the match criteria that apply to\n                        source entity.\n                      properties:\n                        namespaceSelector:\n                          description: \"NamespaceSelector is an optional field that\n                            contains a selector expression. Only traffic that originates\n                            from (or terminates at) endpoints within the selected\n                            namespaces will be matched. When both NamespaceSelector\n                            and another selector are defined on the same rule, then\n                            only workload endpoints that are matched by both selectors\n                            will be selected by the rule. \\n For NetworkPolicy, an\n                            empty NamespaceSelector implies that the Selector is limited\n                            to selecting only workload endpoints in the same namespace\n                            as the NetworkPolicy. \\n For NetworkPolicy, `global()`\n                            NamespaceSelector implies that the Selector is limited\n                            to selecting only GlobalNetworkSet or HostEndpoint. \\n\n                            For GlobalNetworkPolicy, an empty NamespaceSelector implies\n                            the Selector applies to workload endpoints across all\n                            namespaces.\"\n                          type: string\n                        nets:\n                          description: Nets is an optional field that restricts the\n                            rule to only apply to traffic that originates from (or\n                            terminates at) IP addresses in any of the given subnets.\n                          items:\n                            type: string\n                          type: array\n                        notNets:\n                          description: NotNets is the negated version of the Nets\n                            field.\n                          items:\n                            type: string\n                          type: array\n                        notPorts:\n                          description: NotPorts is the negated version of the Ports\n                            field. Since only some protocols have ports, if any ports\n                            are specified it requires the Protocol match in the Rule\n                            to be set to \"TCP\" or \"UDP\".\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        notSelector:\n                          description: NotSelector is the negated version of the Selector\n                            field.  See Selector field for subtleties with negated\n                            selectors.\n                          type: string\n                        ports:\n                          description: \"Ports is an optional field that restricts\n                            the rule to only apply to traffic that has a source (destination)\n                            port that matches one of these ranges/values. This value\n                            is a list of integers or strings that represent ranges\n                            of ports. \\n Since only some protocols have ports, if\n                            any ports are specified it requires the Protocol match\n                            in the Rule to be set to \\\"TCP\\\" or \\\"UDP\\\".\"\n                          items:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^.*\n                            x-kubernetes-int-or-string: true\n                          type: array\n                        selector:\n                          description: \"Selector is an optional field that contains\n                            a selector expression (see Policy for sample syntax).\n                            \\ Only traffic that originates from (terminates at) endpoints\n                            matching the selector will be matched. \\n Note that: in\n                            addition to the negated version of the Selector (see NotSelector\n                            below), the selector expression syntax itself supports\n                            negation.  The two types of negation are subtly different.\n                            One negates the set of matched endpoints, the other negates\n                            the whole match: \\n \\tSelector = \\\"!has(my_label)\\\" matches\n                            packets that are from other Calico-controlled \\tendpoints\n                            that do not have the label \\\"my_label\\\". \\n \\tNotSelector\n                            = \\\"has(my_label)\\\" matches packets that are not from\n                            Calico-controlled \\tendpoints that do have the label \\\"my_label\\\".\n                            \\n The effect is that the latter will accept packets from\n                            non-Calico sources whereas the former is limited to packets\n                            from Calico-controlled endpoints.\"\n                          type: string\n                        serviceAccounts:\n                          description: ServiceAccounts is an optional field that restricts\n                            the rule to only apply to traffic that originates from\n                            (or terminates at) a pod running as a matching service\n                            account.\n                          properties:\n                            names:\n                              description: Names is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account whose name is in the list.\n                              items:\n                                type: string\n                              type: array\n                            selector:\n                              description: Selector is an optional field that restricts\n                                the rule to only apply to traffic that originates\n                                from (or terminates at) a pod running as a service\n                                account that matches the given label selector. If\n                                both Names and Selector are specified then they are\n                                AND'ed.\n                              type: string\n                          type: object\n                        services:\n                          description: \"Services is an optional field that contains\n                            options for matching Kubernetes Services. If specified,\n                            only traffic that originates from or terminates at endpoints\n                            within the selected service(s) will be matched, and only\n                            to/from each endpoint's port. \\n Services cannot be specified\n                            on the same rule as Selector, NotSelector, NamespaceSelector,\n                            Nets, NotNets or ServiceAccounts. \\n Ports and NotPorts\n                            can only be specified with Services on ingress rules.\"\n                          properties:\n                            name:\n                              description: Name specifies the name of a Kubernetes\n                                Service to match.\n                              type: string\n                            namespace:\n                              description: Namespace specifies the namespace of the\n                                given Service. If left empty, the rule will match\n                                within this policy's namespace.\n                              type: string\n                          type: object\n                      type: object\n                  required:\n                  - action\n                  type: object\n                type: array\n              order:\n                description: Order is an optional field that specifies the order in\n                  which the policy is applied. Policies with higher \"order\" are applied\n                  after those with lower order.  If the order is omitted, it may be\n                  considered to be \"infinite\" - i.e. the policy will be applied last.  Policies\n                  with identical order will be applied in alphanumerical order based\n                  on the Policy \"Name\".\n                type: number\n              selector:\n                description: \"The selector is an expression used to pick pick out\n                  the endpoints that the policy should be applied to. \\n Selector\n                  expressions follow this syntax: \\n \\tlabel == \\\"string_literal\\\"\n                  \\ ->  comparison, e.g. my_label == \\\"foo bar\\\" \\tlabel != \\\"string_literal\\\"\n                  \\  ->  not equal; also matches if label is not present \\tlabel in\n                  { \\\"a\\\", \\\"b\\\", \\\"c\\\", ... }  ->  true if the value of label X is\n                  one of \\\"a\\\", \\\"b\\\", \\\"c\\\" \\tlabel not in { \\\"a\\\", \\\"b\\\", \\\"c\\\",\n                  ... }  ->  true if the value of label X is not one of \\\"a\\\", \\\"b\\\",\n                  \\\"c\\\" \\thas(label_name)  -> True if that label is present \\t! expr\n                  -> negation of expr \\texpr && expr  -> Short-circuit and \\texpr\n                  || expr  -> Short-circuit or \\t( expr ) -> parens for grouping \\tall()\n                  or the empty selector -> matches all endpoints. \\n Label names are\n                  allowed to contain alphanumerics, -, _ and /. String literals are\n                  more permissive but they do not support escape characters. \\n Examples\n                  (with made-up labels): \\n \\ttype == \\\"webserver\\\" && deployment\n                  == \\\"prod\\\" \\ttype in {\\\"frontend\\\", \\\"backend\\\"} \\tdeployment !=\n                  \\\"dev\\\" \\t! has(label_name)\"\n                type: string\n              serviceAccountSelector:\n                description: ServiceAccountSelector is an optional field for an expression\n                  used to select a pod based on service accounts.\n                type: string\n              types:\n                description: \"Types indicates whether this policy applies to ingress,\n                  or to egress, or to both.  When not explicitly specified (and so\n                  the value on creation is empty or nil), Calico defaults Types according\n                  to what Ingress and Egress are present in the policy.  The default\n                  is: \\n - [ PolicyTypeIngress ], if there are no Egress rules (including\n                  the case where there are   also no Ingress rules) \\n - [ PolicyTypeEgress\n                  ], if there are Egress rules but no Ingress rules \\n - [ PolicyTypeIngress,\n                  PolicyTypeEgress ], if there are both Ingress and Egress rules.\n                  \\n When the policy is read back again, Types will always be one\n                  of these values, never empty or nil.\"\n                items:\n                  description: PolicyType enumerates the possible values of the PolicySpec\n                    Types field.\n                  type: string\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/kdd-crds.yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networksets.crd.projectcalico.org\nspec:\n  group: crd.projectcalico.org\n  names:\n    kind: NetworkSet\n    listKind: NetworkSetList\n    plural: networksets\n    singular: networkset\n  preserveUnknownFields: false\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: NetworkSet is the Namespaced-equivalent of the GlobalNetworkSet.\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: NetworkSetSpec contains the specification for a NetworkSet\n              resource.\n            properties:\n              nets:\n                description: The list of IP networks that belong to this set.\n                items:\n                  type: string\n                type: array\n            type: object\n        type: object\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: calico/templates/calico-kube-controllers-rbac.yaml\n# Include a clusterrole for the kube-controllers component,\n# and bind it to the calico-kube-controllers serviceaccount.\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: calico-kube-controllers\nrules:\n  # Nodes are watched to monitor for deletions.\n  - apiGroups: [\"\"]\n    resources:\n      - nodes\n    verbs:\n      - watch\n      - list\n      - get\n  # Pods are watched to check for existence as part of IPAM controller.\n  - apiGroups: [\"\"]\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  # IPAM resources are manipulated in response to node and block updates, as well as periodic triggers.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - ipreservations\n    verbs:\n      - list\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - blockaffinities\n      - ipamblocks\n      - ipamhandles\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - delete\n      - watch\n  # Pools are watched to maintain a mapping of blocks to IP pools.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - ippools\n    verbs:\n      - list\n      - watch\n  # kube-controllers manages hostendpoints.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - hostendpoints\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - delete\n  # Needs access to update clusterinformations.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - clusterinformations\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - watch\n  # KubeControllersConfiguration is where it gets its config\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - kubecontrollersconfigurations\n    verbs:\n      # read its own config\n      - get\n      # create a default if none exists\n      - create\n      # update status\n      - update\n      # watch for changes\n      - watch\n---\n# Source: calico/templates/calico-node-rbac.yaml\n# Include a clusterrole for the calico-node DaemonSet,\n# and bind it to the calico-node serviceaccount.\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: calico-node\nrules:\n  # Used for creating service account tokens to be used by the CNI plugin\n  - apiGroups: [\"\"]\n    resources:\n      - serviceaccounts/token\n    resourceNames:\n      - calico-cni-plugin\n    verbs:\n      - create\n  # The CNI plugin needs to get pods, nodes, and namespaces.\n  - apiGroups: [\"\"]\n    resources:\n      - pods\n      - nodes\n      - namespaces\n    verbs:\n      - get\n  # EndpointSlices are used for Service-based network policy rule\n  # enforcement.\n  - apiGroups: [\"discovery.k8s.io\"]\n    resources:\n      - endpointslices\n    verbs:\n      - watch\n      - list\n  - apiGroups: [\"\"]\n    resources:\n      - endpoints\n      - services\n    verbs:\n      # Used to discover service IPs for advertisement.\n      - watch\n      - list\n      # Used to discover Typhas.\n      - get\n  # Pod CIDR auto-detection on kubeadm needs access to config maps.\n  - apiGroups: [\"\"]\n    resources:\n      - configmaps\n    verbs:\n      - get\n  - apiGroups: [\"\"]\n    resources:\n      - nodes/status\n    verbs:\n      # Needed for clearing NodeNetworkUnavailable flag.\n      - patch\n      # Calico stores some configuration information in node annotations.\n      - update\n  # Watch for changes to Kubernetes NetworkPolicies.\n  - apiGroups: [\"networking.k8s.io\"]\n    resources:\n      - networkpolicies\n    verbs:\n      - watch\n      - list\n  # Used by Calico for policy information.\n  - apiGroups: [\"\"]\n    resources:\n      - pods\n      - namespaces\n      - serviceaccounts\n    verbs:\n      - list\n      - watch\n  # The CNI plugin patches pods/status.\n  - apiGroups: [\"\"]\n    resources:\n      - pods/status\n    verbs:\n      - patch\n  # Calico monitors various CRDs for config.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - globalfelixconfigs\n      - felixconfigurations\n      - bgppeers\n      - bgpfilters\n      - globalbgpconfigs\n      - bgpconfigurations\n      - ippools\n      - ipreservations\n      - ipamblocks\n      - globalnetworkpolicies\n      - globalnetworksets\n      - networkpolicies\n      - networksets\n      - clusterinformations\n      - hostendpoints\n      - blockaffinities\n      - caliconodestatuses\n    verbs:\n      - get\n      - list\n      - watch\n  # Calico must create and update some CRDs on startup.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - ippools\n      - felixconfigurations\n      - clusterinformations\n    verbs:\n      - create\n      - update\n  # Calico must update some CRDs.\n  - apiGroups: [ \"crd.projectcalico.org\" ]\n    resources:\n      - caliconodestatuses\n    verbs:\n      - update\n  # Calico stores some configuration information on the node.\n  - apiGroups: [\"\"]\n    resources:\n      - nodes\n    verbs:\n      - get\n      - list\n      - watch\n  # These permissions are only required for upgrade from v2.6, and can\n  # be removed after upgrade or on fresh installations.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - bgpconfigurations\n      - bgppeers\n    verbs:\n      - create\n      - update\n  # These permissions are required for Calico CNI to perform IPAM allocations.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - blockaffinities\n      - ipamblocks\n      - ipamhandles\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - delete\n  # The CNI plugin and calico/node need to be able to create a default\n  # IPAMConfiguration\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - ipamconfigs\n    verbs:\n      - get\n      - create\n  # Block affinities must also be watchable by confd for route aggregation.\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - blockaffinities\n    verbs:\n      - watch\n  # The Calico IPAM migration needs to get daemonsets. These permissions can be\n  # removed if not upgrading from an installation using host-local IPAM.\n  - apiGroups: [\"apps\"]\n    resources:\n      - daemonsets\n    verbs:\n      - get\n---\n# Source: calico/templates/calico-node-rbac.yaml\n# CNI cluster role\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: calico-cni-plugin\nrules:\n  - apiGroups: [\"\"]\n    resources:\n      - pods\n      - nodes\n      - namespaces\n    verbs:\n      - get\n  - apiGroups: [\"\"]\n    resources:\n      - pods/status\n    verbs:\n      - patch\n  - apiGroups: [\"crd.projectcalico.org\"]\n    resources:\n      - blockaffinities\n      - ipamblocks\n      - ipamhandles\n      - clusterinformations\n      - ippools\n      - ipreservations\n      - ipamconfigs\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - delete\n---\n# Source: calico/templates/calico-kube-controllers-rbac.yaml\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: calico-kube-controllers\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: calico-kube-controllers\nsubjects:\n- kind: ServiceAccount\n  name: calico-kube-controllers\n  namespace: kube-system\n---\n# Source: calico/templates/calico-node-rbac.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: calico-node\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: calico-node\nsubjects:\n- kind: ServiceAccount\n  name: calico-node\n  namespace: kube-system\n---\n# Source: calico/templates/calico-node-rbac.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: calico-cni-plugin\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: calico-cni-plugin\nsubjects:\n- kind: ServiceAccount\n  name: calico-cni-plugin\n  namespace: kube-system\n---\n# Source: calico/templates/calico-node.yaml\n# This manifest installs the calico-node container, as well\n# as the CNI plugins and network config on\n# each master and worker node in a Kubernetes cluster.\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: calico-node\n  namespace: kube-system\n  labels:\n    k8s-app: calico-node\nspec:\n  selector:\n    matchLabels:\n      k8s-app: calico-node\n  updateStrategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxUnavailable: 1\n  template:\n    metadata:\n      labels:\n        k8s-app: calico-node\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      hostNetwork: true\n      tolerations:\n        # Make sure calico-node gets scheduled on all nodes.\n        - effect: NoSchedule\n          operator: Exists\n        # Mark the pod as a critical add-on for rescheduling.\n        - key: CriticalAddonsOnly\n          operator: Exists\n        - effect: NoExecute\n          operator: Exists\n      serviceAccountName: calico-node\n      # Minimize downtime during a rolling upgrade or deletion; tell Kubernetes to do a \"force\n      # deletion\": https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods.\n      terminationGracePeriodSeconds: 0\n      priorityClassName: system-node-critical\n      initContainers:\n        # This container performs upgrade from host-local IPAM to calico-ipam.\n        # It can be deleted if this is a fresh installation, or if you have already\n        # upgraded to use calico-ipam.\n        - name: upgrade-ipam\n          image: docker.io/calico/cni:v3.26.4\n          imagePullPolicy: IfNotPresent\n          command: [\"/opt/cni/bin/calico-ipam\", \"-upgrade\"]\n          envFrom:\n          - configMapRef:\n              # Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.\n              name: kubernetes-services-endpoint\n              optional: true\n          env:\n            - name: KUBERNETES_NODE_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            - name: CALICO_NETWORKING_BACKEND\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: calico_backend\n          volumeMounts:\n            - mountPath: /var/lib/cni/networks\n              name: host-local-net-dir\n            - mountPath: /host/opt/cni/bin\n              name: cni-bin-dir\n          securityContext:\n            privileged: true\n        # This container installs the CNI binaries\n        # and CNI network config file on each node.\n        - name: install-cni\n          image: docker.io/calico/cni:v3.26.4\n          imagePullPolicy: IfNotPresent\n          command: [\"/opt/cni/bin/install\"]\n          envFrom:\n          - configMapRef:\n              # Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.\n              name: kubernetes-services-endpoint\n              optional: true\n          env:\n            # Name of the CNI config file to create.\n            - name: CNI_CONF_NAME\n              value: \"10-calico.conflist\"\n            # The CNI network config to install on each node.\n            - name: CNI_NETWORK_CONFIG\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: cni_network_config\n            # Set the hostname based on the k8s node name.\n            - name: KUBERNETES_NODE_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            # CNI MTU Config variable\n            - name: CNI_MTU\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: veth_mtu\n            # Prevents the container from sleeping forever.\n            - name: SLEEP\n              value: \"false\"\n          volumeMounts:\n            - mountPath: /host/opt/cni/bin\n              name: cni-bin-dir\n            - mountPath: /host/etc/cni/net.d\n              name: cni-net-dir\n          securityContext:\n            privileged: true\n        # This init container mounts the necessary filesystems needed by the BPF data plane\n        # i.e. bpf at /sys/fs/bpf and cgroup2 at /run/calico/cgroup. Calico-node initialisation is executed\n        # in best effort fashion, i.e. no failure for errors, to not disrupt pod creation in iptable mode.\n        - name: \"mount-bpffs\"\n          image: docker.io/calico/node:v3.26.4\n          imagePullPolicy: IfNotPresent\n          command: [\"calico-node\", \"-init\", \"-best-effort\"]\n          volumeMounts:\n            - mountPath: /sys/fs\n              name: sys-fs\n              # Bidirectional is required to ensure that the new mount we make at /sys/fs/bpf propagates to the host\n              # so that it outlives the init container.\n              mountPropagation: Bidirectional\n            - mountPath: /var/run/calico\n              name: var-run-calico\n              # Bidirectional is required to ensure that the new mount we make at /run/calico/cgroup propagates to the host\n              # so that it outlives the init container.\n              mountPropagation: Bidirectional\n            # Mount /proc/ from host which usually is an init program at /nodeproc. It's needed by mountns binary,\n            # executed by calico-node, to mount root cgroup2 fs at /run/calico/cgroup to attach CTLB programs correctly.\n            - mountPath: /nodeproc\n              name: nodeproc\n              readOnly: true\n          securityContext:\n            privileged: true\n      containers:\n        # Runs calico-node container on each Kubernetes node. This\n        # container programs network policy and routes on each\n        # host.\n        - name: calico-node\n          image: docker.io/calico/node:v3.26.4\n          imagePullPolicy: IfNotPresent\n          envFrom:\n          - configMapRef:\n              # Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.\n              name: kubernetes-services-endpoint\n              optional: true\n          env:\n            # Use Kubernetes API as the backing datastore.\n            - name: DATASTORE_TYPE\n              value: \"kubernetes\"\n            # Wait for the datastore.\n            - name: WAIT_FOR_DATASTORE\n              value: \"true\"\n            # Set based on the k8s node name.\n            - name: NODENAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            # Choose the backend to use.\n            - name: CALICO_NETWORKING_BACKEND\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: calico_backend\n            # Cluster type to identify the deployment type\n            - name: CLUSTER_TYPE\n              value: \"k8s,bgp\"\n            # Auto-detect the BGP IP address.\n            - name: IP\n              value: \"autodetect\"\n            # Enable IPIP\n            - name: CALICO_IPV4POOL_IPIP\n              value: \"Always\"\n            # Enable or Disable VXLAN on the default IP pool.\n            - name: CALICO_IPV4POOL_VXLAN\n              value: \"Never\"\n            # Enable or Disable VXLAN on the default IPv6 IP pool.\n            - name: CALICO_IPV6POOL_VXLAN\n              value: \"Never\"\n            # Set MTU for tunnel device used if ipip is enabled\n            - name: FELIX_IPINIPMTU\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: veth_mtu\n            # Set MTU for the VXLAN tunnel device.\n            - name: FELIX_VXLANMTU\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: veth_mtu\n            # Set MTU for the Wireguard tunnel device.\n            - name: FELIX_WIREGUARDMTU\n              valueFrom:\n                configMapKeyRef:\n                  name: calico-config\n                  key: veth_mtu\n            # The default IPv4 pool to create on startup if none exists. Pod IPs will be\n            # chosen from this range. Changing this value after installation will have\n            # no effect. This should fall within `--cluster-cidr`.\n            # - name: CALICO_IPV4POOL_CIDR\n            #   value: \"192.168.0.0/16\"\n            # Disable file logging so `kubectl logs` works.\n            - name: CALICO_DISABLE_FILE_LOGGING\n              value: \"true\"\n            # Set Felix endpoint to host default action to ACCEPT.\n            - name: FELIX_DEFAULTENDPOINTTOHOSTACTION\n              value: \"ACCEPT\"\n            # Disable IPv6 on Kubernetes.\n            - name: FELIX_IPV6SUPPORT\n              value: \"false\"\n            - name: FELIX_HEALTHENABLED\n              value: \"true\"\n          securityContext:\n            privileged: true\n          resources:\n            requests:\n              cpu: 250m\n          lifecycle:\n            preStop:\n              exec:\n                command:\n                - /bin/calico-node\n                - -shutdown\n          livenessProbe:\n            exec:\n              command:\n              - /bin/calico-node\n              - -felix-live\n              - -bird-live\n            periodSeconds: 10\n            initialDelaySeconds: 10\n            failureThreshold: 6\n            timeoutSeconds: 10\n          readinessProbe:\n            exec:\n              command:\n              - /bin/calico-node\n              - -felix-ready\n              - -bird-ready\n            periodSeconds: 10\n            timeoutSeconds: 10\n          volumeMounts:\n            # For maintaining CNI plugin API credentials.\n            - mountPath: /host/etc/cni/net.d\n              name: cni-net-dir\n              readOnly: false\n            - mountPath: /lib/modules\n              name: lib-modules\n              readOnly: true\n            - mountPath: /run/xtables.lock\n              name: xtables-lock\n              readOnly: false\n            - mountPath: /var/run/calico\n              name: var-run-calico\n              readOnly: false\n            - mountPath: /var/lib/calico\n              name: var-lib-calico\n              readOnly: false\n            - name: policysync\n              mountPath: /var/run/nodeagent\n            # For eBPF mode, we need to be able to mount the BPF filesystem at /sys/fs/bpf so we mount in the\n            # parent directory.\n            - name: bpffs\n              mountPath: /sys/fs/bpf\n            - name: cni-log-dir\n              mountPath: /var/log/calico/cni\n              readOnly: true\n      volumes:\n        # Used by calico-node.\n        - name: lib-modules\n          hostPath:\n            path: /lib/modules\n        - name: var-run-calico\n          hostPath:\n            path: /var/run/calico\n        - name: var-lib-calico\n          hostPath:\n            path: /var/lib/calico\n        - name: xtables-lock\n          hostPath:\n            path: /run/xtables.lock\n            type: FileOrCreate\n        - name: sys-fs\n          hostPath:\n            path: /sys/fs/\n            type: DirectoryOrCreate\n        - name: bpffs\n          hostPath:\n            path: /sys/fs/bpf\n            type: Directory\n        # mount /proc at /nodeproc to be used by mount-bpffs initContainer to mount root cgroup2 fs.\n        - name: nodeproc\n          hostPath:\n            path: /proc\n        # Used to install CNI.\n        - name: cni-bin-dir\n          hostPath:\n            path: /opt/cni/bin\n        - name: cni-net-dir\n          hostPath:\n            path: /etc/cni/net.d\n        # Used to access CNI logs.\n        - name: cni-log-dir\n          hostPath:\n            path: /var/log/calico/cni\n        # Mount in the directory for host-local IPAM allocations. This is\n        # used when upgrading from host-local to calico-ipam, and can be removed\n        # if not using the upgrade-ipam init container.\n        - name: host-local-net-dir\n          hostPath:\n            path: /var/lib/cni/networks\n        # Used to create per-pod Unix Domain Sockets\n        - name: policysync\n          hostPath:\n            type: DirectoryOrCreate\n            path: /var/run/nodeagent\n---\n# Source: calico/templates/calico-kube-controllers.yaml\n# See https://github.com/projectcalico/kube-controllers\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: calico-kube-controllers\n  namespace: kube-system\n  labels:\n    k8s-app: calico-kube-controllers\nspec:\n  # The controllers can only have a single active instance.\n  replicas: 1\n  selector:\n    matchLabels:\n      k8s-app: calico-kube-controllers\n  strategy:\n    type: Recreate\n  template:\n    metadata:\n      name: calico-kube-controllers\n      namespace: kube-system\n      labels:\n        k8s-app: calico-kube-controllers\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      tolerations:\n        # Mark the pod as a critical add-on for rescheduling.\n        - key: CriticalAddonsOnly\n          operator: Exists\n        - key: node-role.kubernetes.io/master\n          effect: NoSchedule\n        - key: node-role.kubernetes.io/control-plane\n          effect: NoSchedule\n      serviceAccountName: calico-kube-controllers\n      priorityClassName: system-cluster-critical\n      containers:\n        - name: calico-kube-controllers\n          image: docker.io/calico/kube-controllers:v3.26.4\n          imagePullPolicy: IfNotPresent\n          env:\n            # Choose which controllers to run.\n            - name: ENABLED_CONTROLLERS\n              value: node\n            - name: DATASTORE_TYPE\n              value: kubernetes\n          livenessProbe:\n            exec:\n              command:\n              - /usr/bin/check-status\n              - -l\n            periodSeconds: 10\n            initialDelaySeconds: 10\n            failureThreshold: 6\n            timeoutSeconds: 10\n          readinessProbe:\n            exec:\n              command:\n              - /usr/bin/check-status\n              - -r\n            periodSeconds: 10\n"
  },
  {
    "path": "e2e/clusters/eks-with-calico-cni/terraform/main.tf",
    "content": "\n// we spin up an EKS cluster\nmodule \"eks_cluster_calico_cni\" {\n  source = \"../../aws-eks-terraform-module\"\n  region = var.region\n  cluster_name = var.cluster_name\n  cluster_version = var.cluster_version\n  kubeconfig_file = var.kubeconfig_file\n  desired_size = var.desired_size\n  node_group_name = var.node_group_name\n  enable_vpc_network_policies = var.enable_vpc_network_policies\n}\n"
  },
  {
    "path": "e2e/clusters/eks-with-calico-cni/terraform/vars.tf",
    "content": "variable \"region\" {\n  description = \"AWS region\"\n  type        = string\n}\n\nvariable \"cluster_version\" {\n  description = \"The AWS EKS cluster version\"\n  type        = string\n}\n\nvariable \"cluster_name\" {\n  type        = string\n  description = \"name of the cluster and VPC\"\n}\n\nvariable \"kubeconfig_file\" {\n  type        = string\n  description = \"name of the file that contains the kubeconfig information\"\n  default     = \".kubeconfig\"\n}\n\nvariable \"desired_size\" {\n  type        = number\n  description = \"desired size of the worker node pool\"\n  default     = 0\n}\n\nvariable \"node_group_name\" {\n  type        = string\n  description = \"prefix of the node group\"\n  default     = \"group\"\n}\n\nvariable \"enable_vpc_network_policies\" {\n  type        = bool\n  description = \"enable or disable vpc network policies\"\n}\n"
  },
  {
    "path": "e2e/clusters/eks-with-vpc-cni/terraform/main.tf",
    "content": "\n// we spin up an EKS cluster\nmodule \"eks_cluster_vpc_cni\" {\n  source = \"../../aws-eks-terraform-module\"\n  region = var.region\n  cluster_name = var.cluster_name\n  cluster_version = var.cluster_version\n  kubeconfig_file = var.kubeconfig_file\n  desired_size = var.desired_size\n  node_group_name = var.node_group_name\n  enable_vpc_network_policies = var.enable_vpc_network_policies\n}\n\n\n"
  },
  {
    "path": "e2e/clusters/eks-with-vpc-cni/terraform/vars.tf",
    "content": "variable \"region\" {\n  description = \"AWS region\"\n  type        = string\n}\n\nvariable \"cluster_version\" {\n  description = \"The AWS EKS cluster version\"\n  type        = string\n}\n\nvariable \"cluster_name\" {\n  type        = string\n  description = \"name of the cluster and VPC\"\n}\n\nvariable \"kubeconfig_file\" {\n  type        = string\n  description = \"name of the file that contains the kubeconfig information\"\n  default     = \".kubeconfig\"\n}\n\nvariable \"desired_size\" {\n  type        = number\n  description = \"desired size of the worker node pool\"\n  default     = 0\n}\n\nvariable \"node_group_name\" {\n  type        = string\n  description = \"prefix of the node group\"\n  default     = \"group\"\n}\n\nvariable \"enable_vpc_network_policies\" {\n  type        = bool\n  description = \"enable or disable vpc network policies\"\n}\n"
  },
  {
    "path": "e2e/clusters/gke-dataplanev2/main.tf",
    "content": "terraform {\n  required_providers {\n    google = {\n      source  = \"hashicorp/google\"\n      version = \"~> 4.57.0\"\n    }\n  }\n}\n\nprovider \"google\" {\n  zone    = var.zone\n}\n\nresource \"google_container_cluster\" \"e2etest\" {\n  name               = var.cluster_name\n  initial_node_count = 4\n  datapath_provider  = var.use_dataplanev2 ? \"ADVANCED_DATAPATH\" : null\n  ip_allocation_policy {}\n  node_config {\n    machine_type = \"e2-standard-2\"\n  }\n\n  release_channel {\n    channel = var.cluster_version\n  }\n\n  provisioner \"local-exec\" {\n    environment = {\n      KUBECONFIG = var.kubeconfig_file\n    }\n    command = \"gcloud container clusters get-credentials ${self.name} --region ${self.location}\"\n  }\n}\n\n\n\n"
  },
  {
    "path": "e2e/clusters/gke-dataplanev2/variables.tf",
    "content": "variable \"zone\" {\n  type = string\n}\n\nvariable \"cluster_name\" {\n  type = string\n}\n\nvariable \"cluster_version\" {\n  type = string\n}\n\nvariable \"use_dataplanev2\" {\n  type = bool\n}\n\nvariable \"kubeconfig_file\" {\n  type = string\n}"
  },
  {
    "path": "e2e/clusters/gke-vpc/main.tf",
    "content": "terraform {\n  required_providers {\n    google = {\n      source  = \"hashicorp/google\"\n      version = \"~> 4.57.0\"\n    }\n  }\n}\n\nprovider \"google\" {\n  zone    = var.zone\n}\n\nresource \"google_container_cluster\" \"e2etest\" {\n  name               = var.cluster_name\n  initial_node_count = 4\n  addons_config {\n    network_policy_config {\n      disabled = false\n    }\n  }\n  network_policy {\n    enabled = true\n  }\n  ip_allocation_policy {}\n  node_config {\n    machine_type = \"e2-standard-2\"\n  }\n\n  release_channel {\n    channel = var.cluster_version\n  }\n\n  provisioner \"local-exec\" {\n    environment = {\n      KUBECONFIG = var.kubeconfig_file\n    }\n    command = \"gcloud container clusters get-credentials ${self.name} --region ${self.location}\"\n  }\n}\n\n\n\n"
  },
  {
    "path": "e2e/clusters/gke-vpc/variables.tf",
    "content": "variable \"zone\" {\n  type = string\n}\n\nvariable \"cluster_name\" {\n  type = string\n}\n\nvariable \"cluster_version\" {\n  type = string\n}\n\nvariable \"use_dataplanev2\" {\n  type = bool\n}\n\nvariable \"kubeconfig_file\" {\n  type = string\n}"
  },
  {
    "path": "e2e/clusters/kind/kind-config.yaml",
    "content": "---\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n# 1 control plane node and 3 workers\nnodes:\n  # the control plane node config\n  - role: control-plane\n    image: kindest/node:v1.35.0\n  # the three workers\n  - role: worker\n    image: kindest/node:v1.35.0\n  - role: worker\n    image: kindest/node:v1.35.0\n  - role: worker\n    image: kindest/node:v1.35.0\nnetworking:\n  disableDefaultCNI: true\n  podSubnet: 192.168.0.0/16\n"
  },
  {
    "path": "e2e/e2e_test.go",
    "content": "package e2e\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gruntwork-io/terratest/modules/k8s\"\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\n\t\"github.com/controlplaneio/netassert/v2/e2e/helpers\"\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n\t\"github.com/controlplaneio/netassert/v2/internal/engine\"\n\t\"github.com/controlplaneio/netassert/v2/internal/kubeops\"\n\t\"github.com/controlplaneio/netassert/v2/internal/logger\"\n)\n\nconst (\n\tsuffixLength            = 9 // suffix length of the random string to be appended to the container name\n\tsnifferContainerImage   = \"docker.io/controlplane/netassertv2-packet-sniffer:latest\"\n\tsnifferContainerPrefix  = \"netassertv2-sniffer\"\n\tscannerContainerImage   = \"docker.io/controlplane/netassertv2-l4-client:latest\"\n\tscannerContainerPrefix  = \"netassertv2-client\"\n\tpauseInSeconds          = 5 // time to pause before each test case\n\tpacketCaputureInterface = `eth0`\n\ttestCasesFile           = `./manifests/test-cases.yaml`\n\tresultFile              = \"result.log\" // where we write the results\n)\n\nvar (\n\tenvVarKind          = `KIND_E2E_TESTS`\n\tenvVarGKEWithVPC    = `GKE_VPC_E2E_TESTS`\n\tenvVarGKEWithDPv2   = `GKE_DPV2_E2E_TESTS`\n\tenvVarEKSWithVPC    = `EKS_VPC_E2E_TESTS`\n\tenvVarEKSWithCalico = `EKS_CALICO_E2E_TESTS`\n)\n\ntype MinimalK8sObject struct {\n\tKind     string `json:\"kind\"`\n\tMetadata struct {\n\t\tName      string `json:\"name\"`\n\t\tNamespace string `json:\"namespace\"`\n\t} `json:\"metadata\"`\n}\n\nvar denyAllPolicyBody = `\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny-all\nspec:\n  podSelector: {}\n  policyTypes:\n  - Ingress\n  - Egress\n`\n\nfunc TestMain(m *testing.M) {\n\texitVal := m.Run()\n\tos.Exit(exitVal)\n}\n\nfunc TestKind(t *testing.T) {\n\tt.Parallel()\n\n\tif os.Getenv(envVarKind) == \"\" {\n\t\tt.Skipf(\"skipping test associated with Kind as %q environment variable was not set\", envVarKind)\n\t}\n\n\tkind := helpers.NewKindCluster(t, \"./clusters/kind\", \"kind-calico\", helpers.Calico)\n\tcreateTestDestroy(t, kind)\n}\n\nfunc TestGKEWithVPC(t *testing.T) {\n\tt.Parallel()\n\n\tif os.Getenv(envVarGKEWithVPC) == \"\" {\n\t\tt.Skipf(\"skipping test associated with GKE VPC as %q environment variable was not set\", envVarGKEWithVPC)\n\t}\n\n\tgke := helpers.NewGKECluster(t, \"./clusters/gke-vpc\", \"vpc\", helpers.VPC)\n\tcreateTestDestroy(t, gke)\n}\n\nfunc TestGKEWithDataPlaneV2(t *testing.T) {\n\tt.Parallel()\n\n\tif os.Getenv(envVarGKEWithDPv2) == \"\" {\n\t\tt.Skipf(\"skipping test associated with GKE DataPlaneV2 as %q environment variable was not set\", envVarGKEWithDPv2)\n\t}\n\n\tgke := helpers.NewGKECluster(t, \"./clusters/gke-dataplanev2\", \"dataplanev2\", helpers.DataPlaneV2)\n\tcreateTestDestroy(t, gke)\n}\n\nfunc TestEKSWithVPC(t *testing.T) {\n\tt.Parallel()\n\n\tpn := os.Getenv(envVarEKSWithVPC)\n\tif pn == \"\" {\n\t\tt.Skipf(\"skipping test associated with EKS VPC CNI as %q environment variable was not set\", envVarEKSWithVPC)\n\t}\n\n\teks := helpers.NewEKSCluster(t, \"./clusters/eks-with-vpc-cni/terraform\", \"vpc\", helpers.VPC)\n\tcreateTestDestroy(t, eks)\n}\n\nfunc TestEKSWithCalico(t *testing.T) {\n\tt.Parallel()\n\n\tpn := os.Getenv(envVarEKSWithCalico)\n\tif pn == \"\" {\n\t\tt.Skipf(\"skipping test associated with EKS Calico CNI as environment %q was not set\", envVarEKSWithCalico)\n\t}\n\n\teks := helpers.NewEKSCluster(t, \"./clusters/eks-with-calico-cni/terraform\", \"calico\", helpers.Calico)\n\tcreateTestDestroy(t, eks)\n}\n\nfunc waitUntilManifestReady(t *testing.T, svc *kubeops.Service, manifestPath string) []string {\n\ttimeout := 20 * time.Minute\n\tpollTime := 30 * time.Second\n\n\tdata, err := os.ReadFile(manifestPath)\n\trequire.NoError(t, err, \"Failed to read manifest file\")\n\n\tdecoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(data), 4096)\n\tnamespaces := make([]string, 0)\n\n\tfor {\n\t\tvar obj MinimalK8sObject\n\t\terr := decoder.Decode(&obj)\n\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\trequire.NoError(t, err, \"Failed to parse YAML document\")\n\n\t\tif obj.Kind == \"\" || obj.Metadata.Name == \"\" {\n\t\t\tt.Fatalf(\"Found malformed kubernetes  document in YAML\")\n\t\t\tcontinue\n\t\t}\n\n\t\tkind := strings.ToLower(obj.Kind)\n\n\t\tswitch kind {\n\t\tcase \"deployment\", \"daemonset\", \"replicaset\", \"statefulset\", \"pod\":\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\ttargetNs := strings.ToLower(obj.Metadata.Namespace)\n\t\tif targetNs == \"\" {\n\t\t\ttargetNs = \"default\"\n\t\t}\n\n\t\tif !slices.Contains(namespaces, targetNs) {\n\t\t\tnamespaces = append(namespaces, targetNs)\n\t\t}\n\n\t\tif err := svc.WaitForPodInResourceReady(obj.Metadata.Name, targetNs, kind, pollTime, timeout); err != nil {\n\t\t\tt.Fatalf(\"Error while waiting for resource %s to become ready: %s\", obj.Metadata.Name, err.Error())\n\t\t}\n\t}\n\treturn namespaces\n}\n\nfunc createTestDestroy(t *testing.T, gc helpers.GenericCluster) {\n\tdefer gc.Destroy(t) // safe to call also when the cluster has not been created\n\tgc.Create(t)\n\n\tctx := context.Background()\n\tkubeConfig := gc.KubeConfigGet()\n\tsvc, err := kubeops.NewServiceFromKubeConfigFile(kubeConfig, hclog.NewNullLogger())\n\tif err != nil {\n\t\tt.Logf(\"Failed to build kubernetes client: %s\", err)\n\t\tt.Fatal(err)\n\t}\n\n\tif err := svc.PingHealthEndpoint(ctx, \"/healthz\"); err != nil {\n\t\tt.Logf(\"Failed to ping kubernetes server: %s\", err)\n\t\tt.Fatal(err)\n\t}\n\n\tt.Log(\"successfully pinged the k8s server\")\n\n\t// create a new Kubernetes client\n\toptions := k8s.NewKubectlOptions(\"\", kubeConfig, \"\")\n\n\t// let's wait for all the nodes to be ready\n\tk8s.WaitUntilAllNodesReady(t, options, 20, 1*time.Minute)\n\n\t// we apply all the manifests and then run\n\tk8s.KubectlApply(t, options, \"./manifests/workload.yaml\")\n\tnamespaces := waitUntilManifestReady(t, svc, \"./manifests/workload.yaml\")\n\n\tnetAssertTestCases, err := data.ReadTestsFromFile(testCasesFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// create the network policies\n\tk8s.KubectlApply(t, options, \"./manifests/networkpolicies.yaml\")\n\n\t// run the sample tests\n\trunTests(ctx, t, svc, netAssertTestCases)\n\n\tif gc.SkipNetPolTests() {\n\t\treturn\n\t}\n\n\t// read the tests again for a fresh start\n\tnetAssertTestCases, err = data.ReadTestsFromFile(testCasesFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// set the exit to 1 since this time the network policies will block the traffic\n\tfor _, tc := range netAssertTestCases {\n\t\ttc.ExitCode = 1\n\t}\n\n\tfor _, ns := range namespaces {\n\t\tnsKubeOptions := k8s.NewKubectlOptions(\"\", kubeConfig, ns)\n\n\t\tk8s.KubectlApplyFromString(t, nsKubeOptions, denyAllPolicyBody)\n\n\t\tk8s.WaitUntilNetworkPolicyAvailable(t, nsKubeOptions, \"default-deny-all\", 10, 5*time.Second)\n\t\trequire.NoError(t, err, \"Error, the NetworkPolicy should exist in namespace %s\", ns)\n\t}\n\n\t// run the tests with network policies blocking everything\n\trunTests(ctx, t, svc, netAssertTestCases)\n}\n\nfunc runTests(ctx context.Context, t *testing.T, svc *kubeops.Service, netAssertTestCases data.Tests) {\n\tlg := logger.NewHCLogger(\"INFO\", \"netassertv2-e2e\", os.Stdout)\n\ttestRunner := engine.New(svc, lg)\n\n\ttestRunner.RunTests(\n\t\tctx,                    // context to use\n\t\tnetAssertTestCases,     // net assert test cases\n\t\tsnifferContainerPrefix, // prefix used for the sniffer container name\n\t\tsnifferContainerImage,  // sniffer container image location\n\t\tscannerContainerPrefix, // scanner container prefix used in the container name\n\t\tscannerContainerImage,  // scanner container image location\n\t\tsuffixLength,           // length of random string that will be appended to the snifferContainerPrefix and scannerContainerPrefix\n\t\ttime.Duration(pauseInSeconds)*time.Second, // time to pause between each test\n\t\tpacketCaputureInterface,                   // the interface used by the sniffer image to capture traffic\n\t)\n\n\tfh, err := os.Create(resultFile)\n\tif err != nil {\n\t\tt.Log(\"failed to create file\", resultFile, err)\n\t\tt.Fatal(err)\n\t}\n\n\tmr := io.MultiWriter(fh, os.Stdout)\n\tlg = logger.NewHCLogger(\"INFO\", \"netassertv2-e2e\", mr)\n\n\tfailedTestCases := 0\n\n\tfor _, v := range netAssertTestCases {\n\t\t// increment the no. of test cases\n\t\tif v.Pass {\n\t\t\tlg.Info(\"✅ Test Result\", \"Name\", v.Name, \"Pass\", v.Pass)\n\t\t\tcontinue\n\t\t}\n\n\t\tlg.Info(\"❌ Test Result\", \"Name\", v.Name, \"Pass\", v.Pass, \"FailureReason\", v.FailureReason)\n\t\tfailedTestCases++\n\t}\n\n\tif failedTestCases > 0 {\n\t\tt.Fatal(\"e2e tests have failed\", err)\n\t}\n}\n"
  },
  {
    "path": "e2e/helpers/common.go",
    "content": "package helpers\n\nimport \"testing\"\n\nconst (\n\tVPC         NetworkMode = \"vpc\"\n\tDataPlaneV2 NetworkMode = \"dataplanev2\"\n\tCalico      NetworkMode = \"calico\"\n)\n\ntype NetworkMode string\n\ntype GenericCluster interface {\n\tCreate(t *testing.T)\n\tDestroy(t *testing.T)\n\tKubeConfigGet() string\n\tSkipNetPolTests() bool\n}\n"
  },
  {
    "path": "e2e/helpers/eks.go",
    "content": "package helpers\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gruntwork-io/terratest/modules/k8s\"\n\t\"github.com/gruntwork-io/terratest/modules/terraform\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/kubeops\"\n\t\"github.com/controlplaneio/netassert/v2/internal/logger\"\n)\n\ntype EKSCluster struct {\n\tterraformDir   string\n\tregion         string\n\tname           string\n\tversion        string\n\tnetworkMode    NetworkMode\n\tkubeConfig     string\n\tkubeConfigPath string\n\topts           *terraform.Options\n}\n\nfunc NewEKSCluster(t *testing.T, terraformDir, clusterNameSuffix string, nm NetworkMode) *EKSCluster {\n\tname := \"netassert-\" + clusterNameSuffix\n\n\tc := &EKSCluster{\n\t\tterraformDir:   terraformDir,\n\t\tregion:         \"us-east-2\",\n\t\tname:           name,\n\t\tversion:        \"1.34\",\n\t\tnetworkMode:    nm,\n\t\tkubeConfig:     name + \".kubeconfig\",\n\t\tkubeConfigPath: terraformDir + \"/\" + name + \".kubeconfig\",\n\t}\n\n\tvpcNetPol := nm == VPC\n\n\ttv := map[string]interface{}{\n\t\t\"region\":                      c.region,\n\t\t\"cluster_version\":             c.version,\n\t\t\"cluster_name\":                c.name,\n\t\t\"kubeconfig_file\":             c.kubeConfig,\n\t\t\"desired_size\":                3,\n\t\t\"node_group_name\":             \"ng\",\n\t\t\"enable_vpc_network_policies\": vpcNetPol,\n\t}\n\n\tif nm == Calico {\n\t\ttv[\"desired_size\"] = 0\n\t\ttv[\"node_group_name\"] = \"group\"\n\t}\n\n\tc.opts = terraform.WithDefaultRetryableErrors(t, &terraform.Options{\n\t\tTerraformDir: c.terraformDir,\n\t\tVars:         tv,\n\t})\n\treturn c\n}\n\nfunc (g *EKSCluster) Create(t *testing.T) {\n\t// terraform init\n\tterraform.InitAndPlan(t, g.opts)\n\n\t// terraform apply\n\tterraform.Apply(t, g.opts)\n\n\tif g.networkMode == Calico {\n\t\tg.installCalico(t)\n\t}\n}\n\nfunc (g *EKSCluster) installCalico(t *testing.T) {\n\t// once the cluster is ready, we need to follow the instructions here\n\t// https://docs.tigera.io/calico/3.26/getting-started/kubernetes/managed-public-cloud/eks\n\tctx := context.Background()\n\n\tlg := logger.NewHCLogger(\"INFO\", \"netassertv2-e2e-calico\", os.Stdout)\n\n\tsvc, err := kubeops.NewServiceFromKubeConfigFile(g.kubeConfigPath, lg)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to build kubernetes client: %s\", err)\n\t}\n\n\t// kubectl delete daemonset -n kube-system aws-node\n\terr = svc.Client.AppsV1().DaemonSets(\"kube-system\").Delete(\n\t\tctx, \"aws-node\", metav1.DeleteOptions{})\n\n\tif err != nil {\n\t\tif !apierrors.IsNotFound(err) {\n\t\t\tt.Fatalf(\"failed to delete daemonset aws-node in the kube-system namespace\")\n\t\t}\n\t}\n\n\tsvc.Log.Info(\"AWS-CNI\", \"msg\", \"Deleted daemonset aws-node in the kube-system namespace\")\n\t// create a new Kubernetes client using the terratest package\n\toptions := k8s.NewKubectlOptions(\"\", g.kubeConfigPath, \"\")\n\n\t// we now apply calico CNI manifest\n\tk8s.KubectlApply(t, options, g.terraformDir+\"/../calico-3.26.4.yaml\")\n\n\t// update the desired_size variable to 3\n\tg.opts.Vars[\"desired_size\"] = 3\n\tg.opts.Vars[\"node_group_name\"] = \"calico\"\n\n\tnewTFOptions := terraform.WithDefaultRetryableErrors(t, g.opts)\n\t// terraform apply the new options\n\t// this terraform apply should scale up the worker nodes with Calico CNI\n\tif _, err := terraform.InitAndApplyE(t, newTFOptions); err != nil {\n\t\tt.Fatalf(\"failed to run terraform init and apply: %s\", err)\n\t}\n\n\tsvc.Log.Info(\"Sleeping 20 minutes so connectivity from the cluster to the Internet is restored\")\n\ttime.Sleep(20 * time.Minute)\n}\n\nfunc (g *EKSCluster) Destroy(t *testing.T) {\n\tif g.opts != nil {\n\t\tterraform.Destroy(t, g.opts)\n\t}\n}\n\nfunc (g *EKSCluster) KubeConfigGet() string {\n\treturn g.kubeConfigPath\n}\n\nfunc (g *EKSCluster) SkipNetPolTests() bool {\n\treturn false // used to be: return g.networkMode != Calico\n}\n"
  },
  {
    "path": "e2e/helpers/gke.go",
    "content": "package helpers\n\nimport (\n\t\"testing\"\n\n\t\"github.com/gruntwork-io/terratest/modules/terraform\"\n)\n\ntype GKECluster struct {\n\tterraformDir   string\n\tzone           string\n\tname           string\n\tversion        string\n\tnetworkMode    NetworkMode\n\tkubeConfig     string\n\tkubeConfigPath string\n\topts           *terraform.Options\n}\n\nfunc NewGKECluster(t *testing.T, terraformDir, clusterNameSuffix string, nm NetworkMode) *GKECluster {\n\tname := \"netassert-\" + clusterNameSuffix\n\n\tc := &GKECluster{\n\t\tterraformDir:   terraformDir,\n\t\tzone:           \"us-central1-b\",\n\t\tname:           name,\n\t\tversion:        \"REGULAR\",\n\t\tnetworkMode:    nm,\n\t\tkubeConfig:     name + \".kubeconfig\",\n\t\tkubeConfigPath: terraformDir + \"/\" + name + \".kubeconfig\",\n\t}\n\n\tc.opts = terraform.WithDefaultRetryableErrors(t, &terraform.Options{\n\t\tTerraformDir: c.terraformDir,\n\t\tVars: map[string]interface{}{\n\t\t\t\"zone\":            c.zone,\n\t\t\t\"cluster_name\":    c.name,\n\t\t\t\"cluster_version\": c.version,\n\t\t\t\"kubeconfig_file\": c.kubeConfig,\n\t\t\t\"use_dataplanev2\": c.networkMode == DataPlaneV2,\n\t\t},\n\t})\n\treturn c\n}\n\nfunc (g *GKECluster) Create(t *testing.T) {\n\t// terraform init\n\tterraform.InitAndPlan(t, g.opts)\n\n\t// terraform apply\n\tterraform.Apply(t, g.opts)\n}\n\nfunc (g *GKECluster) Destroy(t *testing.T) {\n\tif g.opts != nil {\n\t\tterraform.Destroy(t, g.opts)\n\t}\n}\n\nfunc (g *GKECluster) KubeConfigGet() string {\n\treturn g.kubeConfigPath\n}\n\nfunc (g *GKECluster) SkipNetPolTests() bool {\n\treturn false // network policies are supported by all gke cluster configurations\n}\n"
  },
  {
    "path": "e2e/helpers/kind.go",
    "content": "package helpers\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gruntwork-io/terratest/modules/k8s\"\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n)\n\ntype KindCluster struct {\n\tname           string\n\tnetworkMode    NetworkMode\n\tconfigPath     string\n\tkubeConfigPath string\n\tprovider       *cluster.Provider\n}\n\nfunc NewKindCluster(t *testing.T, WorkspaceDir string, clusterNameSuffix string, nm NetworkMode) *KindCluster {\n\tname := \"netassert-\" + clusterNameSuffix\n\n\tc := &KindCluster{\n\t\tname:           name,\n\t\tnetworkMode:    nm,\n\t\tconfigPath:     WorkspaceDir + \"/kind-config.yaml\",\n\t\tkubeConfigPath: WorkspaceDir + \"/\" + name + \".kubeconfig\",\n\t}\n\n\treturn c\n}\n\nfunc (k *KindCluster) Create(t *testing.T) {\n\tif _, err := os.Stat(k.configPath); os.IsNotExist(err) {\n\t\tt.Fatalf(\"Error: config file %s does not exit\", k.configPath)\n\t}\n\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(cmd.NewLogger()),\n\t)\n\n\tt.Logf(\"Creating cluster %s\", k.name)\n\terr := provider.Create(\n\t\tk.name,\n\t\tcluster.CreateWithKubeconfigPath(k.kubeConfigPath),\n\t\tcluster.CreateWithConfigFile(k.configPath),\n\t\tcluster.CreateWithDisplayUsage(false),\n\t\tcluster.CreateWithDisplaySalutation(false),\n\t)\n\n\tif err != nil {\n\t\tt.Fatalf(\"Error while creating cluster: %v\", err)\n\t}\n\tk.provider = provider\n\n\toptions := k8s.NewKubectlOptions(\"\", k.kubeConfigPath, \"\")\n\tk8s.KubectlApply(t, options, \"https://raw.githubusercontent.com/projectcalico/calico/v3.31.3/manifests/calico.yaml\")\n}\n\nfunc (k *KindCluster) Destroy(t *testing.T) {\n\tif k.provider != nil {\n\t\tt.Logf(\"Deleting cluster %s\", k.name)\n\t\tif err := k.provider.Delete(k.name, k.kubeConfigPath); err != nil {\n\t\t\tt.Errorf(\"Error while deleting cluster %s: %v\", k.name, err)\n\t\t}\n\t}\n\n\t_ = os.Remove(k.kubeConfigPath)\n}\n\nfunc (k *KindCluster) KubeConfigGet() string {\n\treturn k.kubeConfigPath\n}\n\nfunc (k *KindCluster) SkipNetPolTests() bool {\n\treturn false\n}\n"
  },
  {
    "path": "e2e/manifests/networkpolicies.yaml",
    "content": "kind: NetworkPolicy\napiVersion: networking.k8s.io/v1\nmetadata:\n  namespace: web\n  name: web\nspec:\n  podSelector:\n    matchLabels:\n      app: nginx\n  policyTypes:\n    - Egress\n  egress: []"
  },
  {
    "path": "e2e/manifests/test-cases.yaml",
    "content": "---\n- name: busybox-deploy-to-echoserver-deploy\n  type: k8s\n  protocol: tcp\n  targetPort: 8080\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource:\n      kind: deployment\n      name: echoserver\n      namespace: echoserver\n######\n######\n- name: busybox-deploy-to-echoserver-deploy-2\n  type: k8s\n  protocol: udp\n  targetPort: 53\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource:\n      kind: deployment\n      name: echoserver\n      namespace: echoserver\n########\n#########\n- name: fluentd-deamonset-to-echoserver-deploy\n  type: k8s\n  protocol: udp\n  targetPort: 53\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: daemonset\n      name: fluentd\n      namespace: fluentd\n  dst:\n    k8sResource:\n      kind: deployment\n      name: echoserver\n      namespace: echoserver\n#######\n######\n- name: busybox-deploy-to-web-statefulset\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: # this is type endpoint\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource: ## this is type endpoint\n      kind: statefulset\n      name: web\n      namespace: web\n#######\n######\n- name: web-statefulset-to-busybox-deploy\n  type: k8s\n  protocol: tcp\n  targetPort: 8080\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 1\n  src:\n    k8sResource: ## this is type endpoint\n      kind: statefulset\n      name: web\n      namespace: web\n  dst:\n    k8sResource:\n      kind: deployment\n      name: echoserver\n      namespace: echoserver\n#######\n######\n- name: fluentd-daemonset-to-web-statefulset\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: # this is type endpoint\n      kind: daemonset\n      name: fluentd\n      namespace: fluentd\n  dst:\n    k8sResource: ## this is type endpoint\n      kind: statefulset\n      name: web\n      namespace: web\n###\n####\n- name: busybox-deploy-to-control-plane-dot-io\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 269\n  attempts: 10\n  exitCode: 0\n  src:\n    k8sResource: # type endpoint\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    host: # type host or node or machine\n      name: control-plane.io\n###\n###\n- name: test-from-pod1-to-pod2\n  type: k8s\n  protocol: tcp\n  targetPort: 80\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource: ##\n      kind: pod\n      name: pod1\n      namespace: pod1\n  dst:\n    k8sResource:\n      kind: pod\n      name: pod2\n      namespace: pod2\n###\n###\n- name: busybox-deploy-to-fake-host\n  type: k8s\n  protocol: tcp\n  targetPort: 333\n  timeoutSeconds: 269\n  attempts: 3\n  exitCode: 1\n  src:\n    k8sResource: # type endpoint\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    host: # type host or node or machine\n      name: 0.0.0.0"
  },
  {
    "path": "e2e/manifests/workload.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: fluentd\n---\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: fluentd\n  namespace: fluentd\n  labels:\n    k8s-app: fluentd-logging\nspec:\n  selector:\n    matchLabels:\n      name: fluentd-elasticsearch\n  template:\n    metadata:\n      labels:\n        name: fluentd-elasticsearch\n    spec:\n      tolerations:\n        # these tolerations are to have the daemonset runnable on control plane nodes\n        # remove them if your control plane nodes should not run pods\n        - key: node-role.kubernetes.io/control-plane\n          operator: Exists\n          effect: NoSchedule\n        - key: node-role.kubernetes.io/master\n          operator: Exists\n          effect: NoSchedule\n      containers:\n        - name: fluentd-elasticsearch\n          image: fluentd:v1.18-1\n          resources:\n            limits:\n              memory: 200Mi\n            requests:\n              cpu: 100m\n              memory: 200Mi\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: echoserver\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: busybox\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echoserver\n  namespace: echoserver\n  labels:\n    app: echoserver-deploy\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echoserver\n  template:\n    metadata:\n      labels:\n        app: echoserver\n    spec:\n      containers:\n        - name: echoserver\n          image: k8s.gcr.io/e2e-test-images/echoserver:2.5\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: web\n          resources:\n            requests:\n              memory: 64Mi\n              cpu: 300m\n            limits:\n              memory: 64Mi\n              cpu: 400m\n          securityContext:\n            allowPrivilegeEscalation: false\n            privileged: false\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: busybox\n  namespace: busybox\n  labels:\n    app: busybox\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: busybox\n  template:\n    metadata:\n      labels:\n        app: busybox\n    spec:\n      containers:\n        - name: busybox\n          image: busybox\n          command:\n            - sleep\n            - \"360000\"\n          imagePullPolicy: IfNotPresent\n          resources:\n            requests:\n              memory: 64Mi\n              cpu: 300m\n            limits:\n              memory: 64Mi\n              cpu: 400m\n          securityContext:\n            allowPrivilegeEscalation: false\n            privileged: false\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: pod1\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: pod2\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod2\n  namespace: pod2\n  labels:\n    name: pod2\nspec:\n  containers:\n    - name: webserver\n      image: nginx:latest\n      ports:\n        - containerPort: 80\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod1\n  namespace: pod1\n  labels:\n    name: pod1\nspec:\n  containers:\n    - name: busybox\n      image: busybox\n      command:\n      - sleep\n      - \"360000\"\n      imagePullPolicy: IfNotPresent\n      resources:\n        requests:\n          memory: 64Mi\n          cpu: 300m\n        limits:\n          memory: 64Mi\n          cpu: 400m\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: web\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: web\n  namespace: web\nspec:\n  serviceName: \"nginx\"\n  replicas: 2\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: nginx:latest\n          ports:\n            - containerPort: 80\n              name: web\n"
  },
  {
    "path": "fluxcd-demo/README.md",
    "content": "# 🚀 FluxCD Demo Guide\n\nThis guide walks you through setting up a **FluxCD** demo environment using **kind** (Kubernetes in Docker) and a **local Helm chart registry**.  \nYou’ll see how Flux automates Helm releases and how to observe its reconciliation behavior in action while running tests with **NetAssert**.\n\n---\n\n## 🧰 Prerequisites\n\nBefore starting, make sure you have the following tools installed:\n\n- [Docker](https://docs.docker.com/get-docker/)\n- [kubectl](https://kubernetes.io/docs/tasks/tools/)\n- [kind](https://kind.sigs.k8s.io/)\n- [Helm](https://helm.sh/docs/intro/install/)\n\n---\n\n## 🏗️ Step 1: Set Up the Environment\n\n### 1.1 Start a Local Docker Registry\n\nFluxCD can work with OCI-based Helm registries. Start a local Docker registry to host your Helm charts:\n\n```bash\ndocker run -d -p 5000:5000 --restart=always --name registry-5000 registry:2\n```\n\nThis creates a local registry accessible at `localhost:5000`.\n\n---\n\n### 1.2 Create a Kind Cluster\n\nCreate a local Kubernetes cluster using your configuration file:\n\n```bash\nkind create cluster --config kind-cluster.yaml\n```\n\nOnce complete, verify the cluster is ready:\n\n```bash\nkubectl cluster-info\nkubectl get nodes\n```\n\n---\n\n## ⚙️ Step 2: Install FluxCD\n\nRefer to the official documentation for detailed installation instructions:  \n👉 [FluxCD Installation Guide](https://fluxcd.io/flux/installation/)\n\nFor this demo, you can use the following command:\n\n```bash\nkubectl apply -f https://github.com/fluxcd/flux2/releases/download/v2.7.2/install.yaml\n```\n\nVerify that FluxCD is running:\n\n```bash\nkubectl get pods -n flux-system\n```\n\nExpected output should include components like:\n\n```\nhelm-controller\nkustomize-controller\nnotification-controller\nsource-controller\n```\n\nAll should reach the `Running` state.\n\n---\n\n## 📦 Step 3: Package and Push the Helm Chart\n\n### 3.1 Update Chart Versions\n\nBefore packaging, update the NetAssert subchart to a version available in the packages section of this repo.  \n\n---\n\n### 3.2 Package the Helm Chart\n\nRun the following command to package your chart into a `.tgz` archive:\n\n```bash\nhelm package ./helm -d .\n```\n\nThis produces a packaged chart file, for example:\n\n```\n./fluxcd-demo-0.0.1-dev.tgz\n```\n\n---\n\n### 3.3 Push the Chart to the Local Registry\n\nPush the packaged Helm chart to your local OCI registry:\n\n```bash\nhelm push ./fluxcd-demo-0.0.1-dev.tgz oci://localhost:5000/fluxcd/\n```\n\n---\n\n### 3.4 Apply the FluxCD configs\n\nApply the fluxcd-helmconfig.yaml file so FluxCD can release the charts:\n\n```bash\nkubectl apply -f fluxcd-helmconfig.yaml\n```\n\n---\n\n## 🔄 Step 4: Watch Flux Reconcile the Release with NetAssert Tests\n\nFlux continuously monitors and applies Helm releases defined in your cluster.  \nTo observe its behavior, list Helm releases managed by Flux:\n\n```bash\nkubectl get helmreleases\n```\n\nFlux will automatically pull your Helm chart from the registry and apply it.\n\n---\n\n### 🧩 What to Observe\n\n- The **init container** in your k8s deployment object intentionally delay completion.  \n- The **Netassert** job will not be created until the deployment finishes.  \n- Once the deployments completes, Netassert will start running as a Job, and once finished it is going to make the release marked as successful or failed.\n\n---\n\n## 🔁 Step 5: Demonstrate an Upgrade\n\nYou can simulate a Helm chart upgrade to observe Flux’s automated update handling.\n\n1. **Update chart version** — bump your chart version.  \n2. **Repackage** the chart:\n\n   ```bash\n   helm package ./helm -d .\n   ```\n\n3. **Push** the new version to the registry:\n\n   ```bash\n   helm push ./fluxcd-demo-0.0.2-dev.tgz oci://localhost:5000/fluxcd/\n   ```\n\n4. **Watch** Flux detect and reconcile the new version:\n\n   ```bash\n   kubectl get helmreleases -w\n   ```\n\nYou’ll see Flux automatically roll out the new chart and update your resources in place, and then run the NetAssert tests.\n"
  },
  {
    "path": "fluxcd-demo/fluxcd-helmconfig.yaml",
    "content": "apiVersion: source.toolkit.fluxcd.io/v1\nkind: HelmRepository\nmetadata:\n  name: demo-repo\n  namespace: default\nspec:\n  type: \"oci\"\n  insecure: true\n  interval: 10s\n  url: oci://host.docker.internal:5000/fluxcd\n---\napiVersion: helm.toolkit.fluxcd.io/v2\nkind: HelmRelease\nmetadata:\n  name: demo-release\n  namespace: default\nspec:\n  interval: 10s\n  timeout: 5m\n  chart:\n    spec:\n      chart: fluxcd-demo\n      version: '0.0.x-dev'\n      sourceRef:\n        kind: HelmRepository\n        name: demo-repo\n      interval: 1m\n  releaseName: myhelmrelease\n  # valuesFrom:\n  #   - kind: ConfigMap\n  #     name: tests\n  #     valuesKey: test-cases.yaml\n  #     targetPath: testFile"
  },
  {
    "path": "fluxcd-demo/helm/Chart.yaml",
    "content": "apiVersion: v1\ndescription: fluxcd-demo\nname: fluxcd-demo\nversion: 0.0.1-dev\nappVersion: 0.0.1-dev\ndependencies:\n- name: netassert\n  repository: oci://ghcr.io/controlplaneio/charts\n  version: <version>\n"
  },
  {
    "path": "fluxcd-demo/helm/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"fluxcd-demo.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"fluxcd-demo.fullname\" -}}\n{{- if .Values.fullnameOverride -}}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- $name := default .Chart.Name .Values.nameOverride -}}\n{{- if contains $name .Release.Name -}}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"fluxcd-demo.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n"
  },
  {
    "path": "fluxcd-demo/helm/templates/deployment.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: echoserver\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: busybox\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ template \"fluxcd-demo.fullname\" . }}-echoserver\n  namespace: echoserver\n  labels:\n    app: echoserver-deploy\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echoserver\n  template:\n    metadata:\n      labels:\n        app: echoserver\n    spec:\n      initContainers:\n        - name: \"sleepy\"\n          image: busybox:1.36\n          command: [\"sh\", \"-c\", \"echo 'Sleeping...'; sleep 20\"]\n      containers:\n        - name: echoserver\n          image: k8s.gcr.io/e2e-test-images/echoserver:2.5\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: web\n          resources:\n            requests:\n              memory: 64Mi\n              cpu: 300m\n            limits:\n              memory: 64Mi\n              cpu: 400m\n          securityContext:\n            allowPrivilegeEscalation: false\n            privileged: false\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ template \"fluxcd-demo.fullname\" . }}-busybox\n  namespace: busybox\n  labels:\n    app: busybox\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: busybox\n  template:\n    metadata:\n      labels:\n        app: busybox\n    spec:\n      containers:\n        - name: busybox\n          image: busybox\n          command:\n            - sleep\n            - \"360000\"\n          imagePullPolicy: IfNotPresent\n          resources:\n            requests:\n              memory: 64Mi\n              cpu: 300m\n            limits:\n              memory: 64Mi\n              cpu: 400m\n          securityContext:\n            allowPrivilegeEscalation: false\n            privileged: false\n...\n"
  },
  {
    "path": "fluxcd-demo/helm/templates/pod1-pod2.yaml",
    "content": "\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: pod1\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: pod2\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: {{ template \"fluxcd-demo.fullname\" . }}-pod2\n  namespace: pod2\nspec:\n  containers:\n    - name: webserver\n      image: nginx:latest\n      ports:\n        - containerPort: 80\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: {{ template \"fluxcd-demo.fullname\" . }}-pod1\n  namespace: pod1\nspec:\n  containers:\n    - name: busybox\n      image: busybox\n      command:\n      - sleep\n      - \"360000\"\n      imagePullPolicy: IfNotPresent\n      resources:\n        requests:\n          memory: 64Mi\n          cpu: 300m\n        limits:\n          memory: 64Mi\n          cpu: 400m\n\n"
  },
  {
    "path": "fluxcd-demo/helm/templates/post-deploy-tests.yaml",
    "content": "apiVersion: v1\ndata:\n  test.yaml: |\n    ---\n    - name: busybox-deploy-to-echoserver-deploy\n      type: k8s\n      protocol: tcp\n      targetPort: 8080\n      timeoutSeconds: 67\n      attempts: 3\n      exitCode: 0\n      src:\n        k8sResource:\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-busybox\n          namespace: busybox\n      dst:\n        k8sResource:\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-echoserver\n          namespace: echoserver\n    ######\n    ######\n    - name: busybox-deploy-to-echoserver-deploy-2\n      type: k8s\n      protocol: udp\n      targetPort: 53\n      timeoutSeconds: 67\n      attempts: 1\n      exitCode: 0\n      src:\n        k8sResource:\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-busybox\n          namespace: busybox\n      dst:\n        k8sResource:\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-echoserver\n          namespace: echoserver\n    ########\n    #########\n    #######\n    ######\n    - name: busybox-deploy-to-web-statefulset\n      type: k8s\n      protocol: tcp\n      targetPort: 80\n      timeoutSeconds: 67\n      attempts: 3\n      exitCode: 0\n      src:\n        k8sResource: # this is type endpoint\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-busybox\n          namespace: busybox\n      dst:\n        k8sResource: ## this is type endpoint\n          kind: statefulset\n          name: {{ template \"fluxcd-demo.fullname\" . }}-web\n          namespace: web\n    ###\n    ####\n    - name: busybox-deploy-to-control-plane-dot-io\n      type: k8s\n      protocol: tcp\n      targetPort: 80\n      timeoutSeconds: 67\n      attempts: 3\n      exitCode: 0\n      src:\n        k8sResource: # type endpoint\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-busybox\n          namespace: busybox\n      dst:\n        host: # type host or node or machine\n          name: control-plane.io\n    ###\n    ###\n    - name: test-from-pod1-to-pod2\n      type: k8s\n      protocol: tcp\n      targetPort: 80\n      timeoutSeconds: 67\n      attempts: 3\n      exitCode: 0\n      src:\n        k8sResource: ##\n          kind: pod\n          name: {{ template \"fluxcd-demo.fullname\" . }}-pod1\n          namespace: pod1\n      dst:\n        k8sResource:\n          kind: pod\n          name: {{ template \"fluxcd-demo.fullname\" . }}-pod2\n          namespace: pod2\n    ###\n    ###\n    - name: busybox-deploy-to-fake-host\n      type: k8s\n      protocol: tcp\n      targetPort: 333\n      timeoutSeconds: 67\n      attempts: 3\n      exitCode: 1\n      src:\n        k8sResource: # type endpoint\n          kind: deployment\n          name: {{ template \"fluxcd-demo.fullname\" . }}-busybox\n          namespace: busybox\n      dst:\n        host: # type host or node or machine\n          name: 0.0.0.0\nkind: ConfigMap\nmetadata:\n  name: \"{{ .Release.Name }}-netassert\"\n"
  },
  {
    "path": "fluxcd-demo/helm/templates/statefulset.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: web\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: {{ template \"fluxcd-demo.fullname\" . }}-web\n  namespace: web\nspec:\n  serviceName: \"nginx\"\n  replicas: 2\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: nginx:1.28\n          ports:\n            - containerPort: 80\n              name: web\n...\n"
  },
  {
    "path": "fluxcd-demo/helm/values.yaml",
    "content": "\n"
  },
  {
    "path": "fluxcd-demo/kind-cluster.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\ncontainerdConfigPatches:\n  - |-\n    [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"host.docker.internal:5050\"]\n      endpoint = [\"http://host.docker.internal:5050\"]\n    [plugins.\"io.containerd.grpc.v1.cri\".registry.configs.\"host.docker.internal:5050\"]\n      insecure_skip_verify = true"
  },
  {
    "path": "go.mod",
    "content": "module github.com/controlplaneio/netassert/v2\n\ngo 1.25.4\n\nrequire (\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/gruntwork-io/terratest v0.55.0\n\tgithub.com/hashicorp/go-hclog v1.6.3\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/stretchr/testify v1.11.1\n\tgo.uber.org/automaxprocs v1.6.0\n\tgo.uber.org/mock v0.4.0\n\tgopkg.in/yaml.v2 v2.4.0\n\tgopkg.in/yaml.v3 v3.0.1\n\tk8s.io/api v0.35.0\n\tk8s.io/apimachinery v0.35.0\n\tk8s.io/client-go v0.35.0\n\tk8s.io/utils v0.0.0-20260108192941-914a6e750570\n\tsigs.k8s.io/kind v0.31.0\n)\n\nrequire (\n\tal.essio.dev/pkg/shellescape v1.5.1 // indirect\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/BurntSushi/toml v1.5.0 // indirect\n\tgithub.com/agext/levenshtein v1.2.3 // indirect\n\tgithub.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.41.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.19 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/acm v1.37.19 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/autoscaling v1.62.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.63.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ec2 v1.279.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecs v1.71.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/iam v1.53.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/kms v1.49.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/lambda v1.87.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/rds v1.114.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/route53 v1.62.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sns v1.39.11 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sqs v1.42.21 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssm v1.67.8 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect\n\tgithub.com/aws/smithy-go v1.24.0 // indirect\n\tgithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect\n\tgithub.com/boombuler/barcode v1.1.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.13.0 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.6.0 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-errors/errors v1.5.1 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.22.4 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.4 // indirect\n\tgithub.com/go-openapi/swag v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/cmdutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/conv v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/fileutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/jsonname v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/jsonutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/loading v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/mangling v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/netutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/stringutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/typeutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/yamlutils v0.25.4 // indirect\n\tgithub.com/go-sql-driver/mysql v1.9.3 // indirect\n\tgithub.com/google/gnostic-models v0.7.1 // indirect\n\tgithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect\n\tgithub.com/gruntwork-io/go-commons v0.17.2 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-getter/v2 v2.2.3 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-safetemp v1.0.0 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hashicorp/hcl/v2 v2.24.0 // indirect\n\tgithub.com/hashicorp/terraform-json v0.27.2 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jackc/pgpassfile v1.0.0 // indirect\n\tgithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect\n\tgithub.com/jackc/pgx/v5 v5.8.0 // indirect\n\tgithub.com/jackc/puddle/v2 v2.2.2 // indirect\n\tgithub.com/jinzhu/copier v0.4.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.3 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-zglob v0.0.6 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/go-testing-interface v1.14.1 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/moby/spdystream v0.5.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/pquerna/otp v1.5.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/tmccombs/hcl2json v0.6.8 // indirect\n\tgithub.com/ulikunitz/xz v0.5.15 // indirect\n\tgithub.com/urfave/cli/v2 v2.27.7 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect\n\tgithub.com/zclconf/go-cty v1.17.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/crypto v0.47.0 // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/net v0.49.0 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.40.0 // indirect\n\tgolang.org/x/term v0.39.0 // indirect\n\tgolang.org/x/text v0.33.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgolang.org/x/tools v0.41.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n\n// go: github.com/imdario/mergo@v1.0.0: parsing go.mod:\n//\tmodule declares its path as: dario.cat/mergo\n//\t        but was required as: github.com/imdario/mergo\nreplace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16\n"
  },
  {
    "path": "go.sum",
    "content": "al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=\nal.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=\ngithub.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=\ngithub.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=\ngithub.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.19 h1:Gxj3kAlmM+a/VVO4YNsmgHGVUZhSxs0tuVwLIxZBCtM=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.19/go.mod h1:XGq5kImVqQT4HUNbbG+0Y8O74URsPNH7CGPg1s1HW5E=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0=\ngithub.com/aws/aws-sdk-go-v2/service/acm v1.37.19 h1:6BPfgg/Y4Pmrdr8KDwHx2CYkw8qPEaGQ+aixjuAY/0U=\ngithub.com/aws/aws-sdk-go-v2/service/acm v1.37.19/go.mod h1:mhOStWeEa1xP99WNNPstX75qgqWgJycL5H7UwZQbqbo=\ngithub.com/aws/aws-sdk-go-v2/service/autoscaling v1.62.5 h1:3maqUQlVW7C6zAdSknv6V/LInH/RJaDW0kTFcy7dkOw=\ngithub.com/aws/aws-sdk-go-v2/service/autoscaling v1.62.5/go.mod h1:8O5Pj92iNpfw/Fa7WdHbn6YiEjDoVdutz+9PGRNoP3Y=\ngithub.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.63.1 h1:l65dmgr7tO26EcHe6WMdseRnFLoJ2nqdkPz1nJdXfaw=\ngithub.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.63.1/go.mod h1:wvnXh1w1pGS2UpEvPTKSjXYuxiXhuvob/IMaK2AWvek=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.6 h1:LNmvkGzDO5PYXDW6m7igx+s2jKaPchpfbS0uDICywFc=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.6/go.mod h1:ctEsEHY2vFQc6i4KU07q4n68v7BAmTbujv2Y+z8+hQY=\ngithub.com/aws/aws-sdk-go-v2/service/ec2 v1.279.2 h1:MG12Z/W1zzJLkw2gCU2gKZ872rqLM0pi9LdkZ/z3FHc=\ngithub.com/aws/aws-sdk-go-v2/service/ec2 v1.279.2/go.mod h1:Uy+C+Sc58jozdoL1McQr8bDsEvNFx+/nBY+vpO1HVUY=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.55.1 h1:B7f9R99lCF83XlolTg6d6Lvghyto+/VU83ZrneAVfK8=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.55.1/go.mod h1:cpYRXx5BkmS3mwWRKPbWSPKmyAUNL7aLWAPiiinwk/U=\ngithub.com/aws/aws-sdk-go-v2/service/ecs v1.71.0 h1:MzP/ElwTpINq+hS80ZQz4epKVnUTlz8Sz+P/AFORCKM=\ngithub.com/aws/aws-sdk-go-v2/service/ecs v1.71.0/go.mod h1:pMlGFDpHoLTJOIZHGdJOAWmi+xeIlQXuFTuQxs1epYE=\ngithub.com/aws/aws-sdk-go-v2/service/iam v1.53.2 h1:62G6btFUwAa5uR5iPlnlNVAM0zJSLbWgDfKOfUC7oW4=\ngithub.com/aws/aws-sdk-go-v2/service/iam v1.53.2/go.mod h1:av9clChrbZbJ5E21msSsiT2oghl2BJHfQGhCkXmhyu8=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17 h1:Nhx/OYX+ukejm9t/MkWI8sucnsiroNYNGb5ddI9ungQ=\ngithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.17/go.mod h1:AjmK8JWnlAevq1b1NBtv5oQVG4iqnYXUufdgol+q9wg=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.49.5 h1:DKibav4XF66XSeaXcrn9GlWGHos6D/vJ4r7jsK7z5CE=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.49.5/go.mod h1:1SdcmEGUEQE1mrU2sIgeHtcMSxHuybhPvuEPANzIDfI=\ngithub.com/aws/aws-sdk-go-v2/service/lambda v1.87.1 h1:QBdmTXWwqVgx0PueT/Xgp2+al5HR0gAV743pTzYeBRw=\ngithub.com/aws/aws-sdk-go-v2/service/lambda v1.87.1/go.mod h1:ogjbkxFgFOjG3dYFQ8irC92gQfpfMDcy1RDKNSZWXNU=\ngithub.com/aws/aws-sdk-go-v2/service/rds v1.114.0 h1:p9c6HDzx6sTf7uyc9xsQd693uzArsPrsVr9n0oRk7DU=\ngithub.com/aws/aws-sdk-go-v2/service/rds v1.114.0/go.mod h1:JBRYWpz5oXQtHgQC+X8LX9lh0FBCwRHJlWEIT+TTLaE=\ngithub.com/aws/aws-sdk-go-v2/service/route53 v1.62.1 h1:1jIdwWOulae7bBLIgB36OZ0DINACb1wxM6wdGlx4eHE=\ngithub.com/aws/aws-sdk-go-v2/service/route53 v1.62.1/go.mod h1:tE2zGlMIlxWv+7Otap7ctRp3qeKqtnja7DZguj3Vu/Y=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 h1:C2dUPSnEpy4voWFIq3JNd8gN0Y5vYGDo44eUE58a/p8=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.95.1/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo=\ngithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.1 h1:72DBkm/CCuWx2LMHAXvLDkZfzopT3psfAeyZDIt1/yE=\ngithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.1/go.mod h1:A+oSJxFvzgjZWkpM0mXs3RxB5O1SD6473w3qafOC9eU=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=\ngithub.com/aws/aws-sdk-go-v2/service/sns v1.39.11 h1:Ke7RS0NuP9Xwk31prXYcFGA1Qfn8QmNWcxyjKPcXZdc=\ngithub.com/aws/aws-sdk-go-v2/service/sns v1.39.11/go.mod h1:hdZDKzao0PBfJJygT7T92x2uVcWc/htqlhrjFIjnHDM=\ngithub.com/aws/aws-sdk-go-v2/service/sqs v1.42.21 h1:Oa0IhwDLVrcBHDlNo1aosG4CxO4HyvzDV5xUWqWcBc0=\ngithub.com/aws/aws-sdk-go-v2/service/sqs v1.42.21/go.mod h1:t98Ssq+qtXKXl2SFtaSkuT6X42FSM//fnO6sfq5RqGM=\ngithub.com/aws/aws-sdk-go-v2/service/ssm v1.67.8 h1:31Llf5VfrZ78YvYs7sWcS7L2m3waikzRc6q1nYenVS4=\ngithub.com/aws/aws-sdk-go-v2/service/ssm v1.67.8/go.mod h1:/jgaDlU1UImoxTxhRNxXHvBAPqPZQ8oCjcPbbkR6kac=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=\ngithub.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=\ngithub.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=\ngithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=\ngithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=\ngithub.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=\ngithub.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=\ngithub.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=\ngithub.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\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-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=\ngithub.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=\ngithub.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=\ngithub.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=\ngithub.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=\ngithub.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=\ngithub.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=\ngithub.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=\ngithub.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=\ngithub.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=\ngithub.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=\ngithub.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=\ngithub.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=\ngithub.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=\ngithub.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=\ngithub.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=\ngithub.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=\ngithub.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=\ngithub.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=\ngithub.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=\ngithub.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=\ngithub.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=\ngithub.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=\ngithub.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=\ngithub.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=\ngithub.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=\ngithub.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=\ngithub.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=\ngithub.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=\ngithub.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=\ngithub.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=\ngithub.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=\ngithub.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=\ngithub.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=\ngithub.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\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/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=\ngithub.com/gruntwork-io/go-commons v0.17.2 h1:14dsCJ7M5Vv2X3BIPKeG9Kdy6vTMGhM8L4WZazxfTuY=\ngithub.com/gruntwork-io/go-commons v0.17.2/go.mod h1:zs7Q2AbUKuTarBPy19CIxJVUX/rBamfW8IwuWKniWkE=\ngithub.com/gruntwork-io/terratest v0.55.0 h1:NgG6lm2dArdQ3KcOofw6PTfVRK1Flt7L3NNhFSBo72A=\ngithub.com/gruntwork-io/terratest v0.55.0/go.mod h1:OE0Jsc8Wn5kw/QySLbBd53g9Gt+xfDyDKChwRHwkKvI=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-getter/v2 v2.2.3 h1:6CVzhT0KJQHqd9b0pK3xSP0CM/Cv+bVhk+jcaRJ2pGk=\ngithub.com/hashicorp/go-getter/v2 v2.2.3/go.mod h1:hp5Yy0GMQvwWVUmwLs3ygivz1JSLI323hdIE9J9m7TY=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=\ngithub.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=\ngithub.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=\ngithub.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=\ngithub.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=\ngithub.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=\ngithub.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=\ngithub.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=\ngithub.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=\ngithub.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=\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/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=\ngithub.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=\ngithub.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=\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/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=\ngithub.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=\ngithub.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=\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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=\ngithub.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=\ngithub.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=\ngithub.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=\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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\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.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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=\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/tmccombs/hcl2json v0.6.8 h1:9bd7c3jZTj9FsN+lDIzrvLmXqxvCgydb84Uc4DBxOHA=\ngithub.com/tmccombs/hcl2json v0.6.8/go.mod h1:qjEaQ4hBNPeDWOENB9yg6+BzqvtMA1MMN1+goFFh8Vc=\ngithub.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=\ngithub.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=\ngithub.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=\ngithub.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=\ngithub.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=\ngithub.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=\ngithub.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=\ngithub.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=\ngo.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=\ngolang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\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.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=\nk8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=\nk8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=\nk8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=\nk8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=\nk8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ=\nk8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=\nk8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/kind v0.31.0 h1:UcT4nzm+YM7YEbqiAKECk+b6dsvc/HRZZu9U0FolL1g=\nsigs.k8s.io/kind v0.31.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "helm/Chart.yaml",
    "content": "apiVersion: v1\ndescription: NetAssert\nname: netassert\nversion: 1.0.0-dev\nappVersion: 1.0.0-dev\nhome: https://github.com/controlplaneio/netassert\nsources:\n- https://github.com/controlplaneio/netassert\n"
  },
  {
    "path": "helm/README.md",
    "content": ""
  },
  {
    "path": "helm/templates/NOTES.txt",
    "content": ""
  },
  {
    "path": "helm/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"netassert.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"netassert.fullname\" -}}\n{{- if .Values.fullnameOverride -}}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- $name := default .Chart.Name .Values.nameOverride -}}\n{{- if contains $name .Release.Name -}}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"netassert.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nAdd Helm annotations when used as a post-deployment tast.\n*/}}\n{{- define \"netassert.hookAnnotations\" -}}\n{{- if eq .Values.mode \"post-deploy\" }}\nhelm.sh/hook: post-install,post-upgrade\nhelm.sh/hook-weight: \"0\"\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/clusterrole.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ template \"netassert.fullname\" . }}\n  annotations:\n    {{- include \"netassert.hookAnnotations\" . | nindent 4 }}\n  labels:\n    app: {{ template \"netassert.name\" . }}\n    chart: {{ template \"netassert.chart\" . }}\n    release: {{ .Release.Name }}\n    heritage: {{ .Release.Service }}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n  - apiGroups: [\"\"]\n    resources: [\"pods/ephemeralcontainers\"]\n    verbs: [\"patch\"]\n  - apiGroups: [\"apps\"]\n    resources: [\"replicasets\"]\n    verbs: [\"get\", \"list\"]\n  - apiGroups: [\"apps\"]\n    resources: [\"deployments\", \"statefulsets\", \"daemonsets\"]\n    verbs: [\"get\"]"
  },
  {
    "path": "helm/templates/clusterrolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ template \"netassert.fullname\" . }}\n  annotations:\n    {{- include \"netassert.hookAnnotations\" . | nindent 4 }}\n  labels:\n    app: {{ template \"netassert.name\" . }}\n    chart: {{ template \"netassert.chart\" . }}\n    release: {{ .Release.Name }}\n    heritage: {{ .Release.Service }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ template \"netassert.fullname\" . }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ template \"netassert.fullname\" . }}\n    namespace: {{ .Release.Namespace }}"
  },
  {
    "path": "helm/templates/configmap.yaml",
    "content": "{{- if ne .Values.mode \"post-deploy\" }}\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ template \"netassert.fullname\" . }}\n  annotations:\n    {{- include \"netassert.hookAnnotations\" . | nindent 4 }}\n  labels:\n    app: {{ template \"netassert.name\" . }}\n    chart: {{ template \"netassert.chart\" . }}\n    release: {{ .Release.Name }}\n    heritage: {{ .Release.Service }}\ndata:\n  test.yaml: |-\n{{ .Values.testFile | indent 4 }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/job.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: {{ template \"netassert.fullname\" . }}\n  annotations:\n    {{- include \"netassert.hookAnnotations\" . | nindent 4 }}\n  labels:\n    app: {{ template \"netassert.name\" . }}\n    chart: {{ template \"netassert.chart\" . }}\n    release: {{ .Release.Name }}\n    heritage: {{ .Release.Service }}\nspec:\n  parallelism: {{ .Values.job.parallelism }}\n  completions: {{ .Values.job.completions }}\n  activeDeadlineSeconds: {{ .Values.job.activeDeadlineSeconds }}\n  backoffLimit: {{ .Values.job.backoffLimit }}\n  ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }}\n  template:\n    metadata:\n      labels:\n        app: {{ template \"netassert.name\" . }}\n        release: {{ .Release.Name }}\n        component: job\n    spec:\n      restartPolicy: {{ default \"Never\" .Values.job.restartPolicy }}\n      serviceAccount: {{ template \"netassert.fullname\" . }}\n      securityContext:\n        {{ toYaml .Values.securityContext | nindent 8 }}\n      {{- if .Values.priorityClassName }}\n      priorityClassName: \"{{ .Values.priorityClassName }}\"\n      {{- end }}\n      terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}\n\n      containers:\n        - name: netassert\n          image: \"{{ .Values.image.repository }}:{{ .Values.image.tag }}\"\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          args:\n            {{ toYaml .Values.args | nindent 12 }}\n          env:\n          {{- range $key, $value := .Values.env }}\n            - name: {{ $key | upper | replace \".\" \"_\" }}\n              value: {{ $value | quote }}\n          {{- end }}\n          resources:\n            {{ toYaml .Values.resources | nindent 12 }}\n          volumeMounts:\n            - name: testfile\n              mountPath: /tests\n            {{- if .Values.volumeMounts }}\n            {{ toYaml .Values.volumeMounts | nindent 12 }}\n            {{- end }}\n\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n\n      volumes:\n        - name: testfile\n          configMap:\n            name: {{ template \"netassert.fullname\" . }}\n        {{- if .Values.volumes }}\n        {{ toYaml .Values.volumes | nindent 8 }}\n        {{- end }}\n        {{- range $key, $value := .Values.secretMounts }}\n        - name: {{ $key }}\n          secret:\n            secretName: {{ $value.secretName }}\n            defaultMode: {{ $value.defaultMode }}\n        {{- end }}"
  },
  {
    "path": "helm/templates/serviceaccount.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ template \"netassert.fullname\" . }}\n  annotations:\n    {{- include \"netassert.hookAnnotations\" . | nindent 4 }}\n  labels:\n    app: {{ template \"netassert.name\" . }}\n    chart: {{ template \"netassert.chart\" . }}\n    release: {{ .Release.Name }}\n    heritage: {{ .Release.Service }}"
  },
  {
    "path": "helm/values.yaml",
    "content": "\n\nmode: post-deploy\njob:\n  parallelism: 1\n  completions: 1\n  activeDeadlineSeconds: 900\n  backoffLimit: 0\n  ttlSecondsAfterFinished: 3600\n  restartPolicy: Never\nterminationGracePeriodSeconds: 30\nserviceAccount:\nimage:\n  repository: controlplane/netassert\n  tag: 1.0.0-dev\n  pullPolicy: IfNotPresent\nargs:\n  - run\n  - --input-file\n  - /tests/test.yaml\nresources: {}\npriorityClassName: \"\"\nnodeSelector: {}\ntolerations: []\naffinity: {}\nsecurityContext: {}\nenv: {}\nvolumes:\nvolumeMounts:\n"
  },
  {
    "path": "internal/data/read.go",
    "content": "package data\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// List of file extensions we support\nconst (\n\tfileExtensionYAML = `.yaml`\n\tfileExtensionYML  = `.yml`\n)\n\n// ReadTestsFromDir - Reads tests cases from .yaml and .yml file present in a directory\n// does not recursively read files\nfunc ReadTestsFromDir(path string) (Tests, error) {\n\tif path == \"\" {\n\t\treturn nil, fmt.Errorf(\"input dir parameter cannot be empty string\")\n\t}\n\n\tvar testCases Tests\n\n\tfp, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open dir containing tests %q: %w\", path, err)\n\t}\n\n\t// we do not recursively read all the YAML and YML files\n\t// the depth is only 1 level\n\tfiles, err := fp.ReadDir(0)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to read contents of the directory%q: %w\", path, err)\n\t}\n\n\tfor _, f := range files {\n\n\t\text := filepath.Ext(f.Name())\n\n\t\tif ext != fileExtensionYAML && ext != fileExtensionYML {\n\t\t\tcontinue\n\t\t}\n\n\t\ttcFile := filepath.Join(path, f.Name())\n\n\t\ttc, err := ReadTestsFromFile(tcFile)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttestCases = append(testCases, tc...)\n\n\t\t// this is a multi-files validation each time new tests are added\n\t\tif err := testCases.Validate(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"validation of tests from file %q failed: %w\", tcFile, err)\n\t\t}\n\n\t}\n\n\treturn testCases, nil\n}\n\n// ReadTestsFromFile - reads tests from a file containing a list of Test\nfunc ReadTestsFromFile(fileName string) (Tests, error) {\n\tif fileName == \"\" {\n\t\treturn nil, fmt.Errorf(\"input fileName parameter can not be empty string\")\n\t}\n\n\tif fileName == \"-\" {\n\t\treturn NewFromReader(os.Stdin)\n\t}\n\n\tfp, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open file %q containing tests: %w\", fileName, err)\n\t}\n\n\tdefer func() {\n\t\tcloseErr := fp.Close()\n\t\tif closeErr != nil {\n\t\t\terr = fmt.Errorf(\"unable to close file: %q, %w\", fileName, closeErr)\n\t\t}\n\t}()\n\n\treturn NewFromReader(fp)\n}\n"
  },
  {
    "path": "internal/data/read_test.go",
    "content": "package data\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReadTestFile(t *testing.T) {\n\tt.Parallel()\n\ttests := map[string]struct {\n\t\tconfFilepath   string\n\t\twantErrMatches []string\n\t}{\n\t\t\"existing valid file\": {\n\t\t\tconfFilepath: \"./testdata/valid/empty.yaml\",\n\t\t},\n\t\t\"existing invalid file\": {\n\t\t\tconfFilepath:   \"./testdata/invalid/not-a-list.yaml\",\n\t\t\twantErrMatches: []string{\"failed to unmarshal tests\"},\n\t\t},\n\t\t\"not existing file\": {\n\t\t\tconfFilepath:   \"./testdata/fake-dir/fake-file.yaml\",\n\t\t\twantErrMatches: []string{\"no such file or directory\"},\n\t\t},\n\t\t\"empty file path\": {\n\t\t\tconfFilepath:   \"\",\n\t\t\twantErrMatches: []string{\"input fileName parameter can not be empty string\"},\n\t\t},\n\t}\n\n\tfor name, tt := range tests {\n\t\ttc := tt\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\t_, err := ReadTestsFromFile(tc.confFilepath)\n\n\t\t\trequire.NotEqualf(t, len(tc.wantErrMatches) > 0, err == nil, \"expecting an error: %v, got: %v\",\n\t\t\t\ttc.wantErrMatches, err)\n\t\t\tfor _, wem := range tc.wantErrMatches {\n\t\t\t\trequire.Equalf(t, strings.Contains(err.Error(), wem), true,\n\t\t\t\t\t\"expecting error to contain: %s, got: %v\", wem, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReadTestsFromDir(t *testing.T) {\n\tt.Parallel()\n\ttests := map[string]struct {\n\t\tconfDir string\n\t\terrMsg  string\n\t\twantErr bool\n\t}{\n\t\t\"existing dir valid tests\": {\n\t\t\tconfDir: \"./testdata/valid\",\n\t\t},\n\t\t\"dir without yaml files\": {\n\t\t\tconfDir: \"./testdata/dir-without-yaml-files\",\n\t\t},\n\t\t\"existing dir with invalid tests\": {\n\t\t\tconfDir: \"./testdata/invalid\",\n\t\t\terrMsg:  \"failed to unmarshal tests\",\n\t\t\twantErr: true,\n\t\t},\n\t\t\"not existing dir\": {\n\t\t\tconfDir: \"./testdata/fake-dir\",\n\t\t\terrMsg:  \"no such file or directory\",\n\t\t\twantErr: true,\n\t\t},\n\t\t\"duplicated test names in different files\": {\n\t\t\tconfDir: \"./testdata/invalid-duplicated-names\",\n\t\t\terrMsg:  \"duplicate test name found\",\n\t\t\twantErr: true,\n\t\t},\n\t\t\"empty file path\": {\n\t\t\tconfDir: \"\",\n\t\t\terrMsg:  \"input dir parameter cannot be empty string\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor name, tt := range tests {\n\t\ttc := tt\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tr := require.New(t)\n\t\t\tt.Parallel()\n\n\t\t\t_, err := ReadTestsFromDir(tc.confDir)\n\n\t\t\tif tc.wantErr {\n\t\t\t\t// we are expecting an error here\n\t\t\t\tr.Error(err)\n\t\t\t\tr.Contains(err.Error(), tc.errMsg)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tr.NoError(err)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/data/tap.go",
    "content": "package data\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"gopkg.in/yaml.v2\"\n)\n\n// TAPResult - outputs result of tests into a TAP format\nfunc (ts *Tests) TAPResult(w io.Writer) error {\n\tif ts == nil {\n\t\treturn fmt.Errorf(\"empty ts\")\n\t}\n\n\tif len(*ts) < 1 {\n\t\treturn fmt.Errorf(\"no test were found\")\n\t}\n\n\theader := \"TAP version 14\\n\"\n\theader += fmt.Sprintf(\"1..%v\\n\", len(*ts))\n\t_, err := fmt.Fprint(w, header)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor index, test := range *ts {\n\t\tresult := \"\"\n\t\tswitch test.Pass {\n\t\tcase true:\n\t\t\tresult = fmt.Sprintf(\"ok %v - %v\", index+1, test.Name)\n\t\tcase false:\n\t\t\tfrEscaped, err := yaml.Marshal(&test.FailureReason) // frEscaped ends with \"\\n\"\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresult = fmt.Sprintf(\"not ok %v - %v\", index+1, test.Name)\n\t\t\tresult += fmt.Sprintf(\"\\n  ---\\n  reason: %s  ...\", frEscaped)\n\t\t}\n\n\t\tif _, err := fmt.Fprintln(w, result); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/data/tap_test.go",
    "content": "package data\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTests_TAPResult(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\ttests   Tests\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"multiple tests\",\n\t\t\ttests: Tests{\n\t\t\t\t&Test{Name: \"test1\", Pass: false, FailureReason: \"example failure reason\"},\n\t\t\t\t&Test{Name: \"pod2pod\", Pass: true},\n\t\t\t\t&Test{Name: \"---\", Pass: true},\n\t\t\t\t&Test{Name: \"don'tknow\", Pass: false},\n\t\t\t},\n\t\t\twant: `TAP version 14\n1..4\nnot ok 1 - test1\n  ---\n  reason: example failure reason\n  ...\nok 2 - pod2pod\nok 3 - ---\nnot ok 4 - don'tknow\n  ---\n  reason: \"\"\n  ...\n`,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"emptytests\",\n\t\t\ttests:   Tests{},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"niltests\",\n\t\t\ttests:   nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tw := &bytes.Buffer{}\n\t\t\terr := tt.tests.TAPResult(w)\n\t\t\tif !tt.wantErr {\n\t\t\t\trequire.NoErrorf(t, err, \"Tests.TAPResult() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tgotW := w.String()\n\t\t\trequire.Equalf(t, tt.want, gotW, \"Tests.TAPResult() = %v, want %v\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/data/testdata/dir-without-yaml-files/.gitkeep",
    "content": ""
  },
  {
    "path": "internal/data/testdata/invalid/duplicated-names.yaml",
    "content": "- name: testname\n  type: k8s\n  targetPort: 80\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"1.1.1.1\"\n- name: testname\n  type: k8s\n  targetPort: 80\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"1.1.1.1\"\n"
  },
  {
    "path": "internal/data/testdata/invalid/empty-resources.yaml",
    "content": "- name: testname\n  type: k8s\n  targetPort: 100000\n  exitCode: 0\n  src:\n    k8sResource:\n      dumb: 44\n  dst:\n    host:"
  },
  {
    "path": "internal/data/testdata/invalid/host-as-dst-udp.yaml",
    "content": "- name: testname\n  type: k8s\n  protocol: udp\n  targetPort: 80\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"1.1.1.1\"\n"
  },
  {
    "path": "internal/data/testdata/invalid/host-as-source.yaml",
    "content": "- name: testname\n  type: k8s\n  targetPort: 80\n  exitCode: 0\n  src:\n    host:\n      name: \"1.1.1.1\"\n  dst:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n"
  },
  {
    "path": "internal/data/testdata/invalid/missing-fields.yaml",
    "content": "- attempts: -2\n  timeoutSeconds: -2\n  targetPort: 100000\n  exitCode: 0\n  src:\n    wrong: 44\n  dst:"
  },
  {
    "path": "internal/data/testdata/invalid/multiple-dst-blocks.yaml",
    "content": "- name: testname2\n  type: k8s\n  protocol: udp\n  timeoutSeconds: 50\n  attempts: 15\n  targetPort: 8080\n  exitCode: 1\n  src:\n    k8sResource:\n      kind: statefulset\n      name: statefulset1\n      namespace: ns1\n  dst:\n    k8sResource:\n      kind: pod\n      name: mypod\n      namespace: ns2\n    host:\n      name: \"1.1.1.1\"\n"
  },
  {
    "path": "internal/data/testdata/invalid/not-a-list.yaml",
    "content": "this:\nshould: \"be\"\na: \"list\""
  },
  {
    "path": "internal/data/testdata/invalid/wrong-test-values.yaml",
    "content": "- name: testname\n  type: k8s\n  protocol: nonexisting\n  attempts: -2\n  timeoutSeconds: -2\n  targetPort: 100000\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: newkind\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"\"\n"
  },
  {
    "path": "internal/data/testdata/invalid-duplicated-names/input1.yaml",
    "content": "- name: testname\n  type: k8s\n  targetPort: 80\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"1.1.1.1\"\n"
  },
  {
    "path": "internal/data/testdata/invalid-duplicated-names/input2.yaml",
    "content": "- name: testname\n  type: k8s\n  targetPort: 80\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"1.1.1.1\"\n"
  },
  {
    "path": "internal/data/testdata/valid/empty.yaml",
    "content": ""
  },
  {
    "path": "internal/data/testdata/valid/multi.yaml",
    "content": "- name: testname\n  type: k8s\n  targetPort: 80\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: deployment1\n      namespace: ns1\n  dst:\n    host:\n      name: \"1.1.1.1\"\n- name: testname2\n  type: k8s\n  protocol: udp\n  timeoutSeconds: 50\n  attempts: 20\n  targetPort: 8080\n  exitCode: 1\n  src:\n    k8sResource:\n      kind: statefulset\n      name: statefulset1\n      namespace: ns1\n  dst:\n    k8sResource:\n      kind: pod\n      name: mypod\n      namespace: ns2\n"
  },
  {
    "path": "internal/data/types.go",
    "content": "package data\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"gopkg.in/yaml.v3\"\n)\n\n// Protocol - represents the Layer 4 protocol\ntype Protocol string\n\nconst (\n\t// ProtocolTCP - represents the TCP protocol\n\tProtocolTCP Protocol = \"tcp\"\n\n\t// ProtocolUDP - represents the UDP protocol\n\tProtocolUDP Protocol = \"udp\"\n)\n\n// K8sResourceKind represents the Kind of K8sResource\ntype K8sResourceKind string\n\nconst (\n\tKindDeployment  K8sResourceKind = \"deployment\"\n\tKindStatefulSet K8sResourceKind = \"statefulset\"\n\tKindDaemonSet   K8sResourceKind = \"daemonset\"\n\tKindPod         K8sResourceKind = \"pod\"\n)\n\n// ValidK8sResourceKinds - holds a map of valid K8sResourceKind\nvar ValidK8sResourceKinds = map[K8sResourceKind]bool{\n\tKindDeployment:  true,\n\tKindStatefulSet: true,\n\tKindDaemonSet:   true,\n\tKindPod:         true,\n}\n\n// TestType - represents a K8s test type, right now\n// we only support k8s type\ntype TestType string\n\nconst (\n\tK8sTest TestType = \"k8s\"\n)\n\n// TestTypes - holds a map of valid NetAsserTestTypes\nvar TestTypes = map[TestType]bool{\n\tK8sTest: true,\n}\n\n// K8sResource - Resource hold a Kubernetes Resource\ntype K8sResource struct {\n\tKind      K8sResourceKind `yaml:\"kind\"`\n\tName      string          `yaml:\"name\"`\n\tNamespace string          `yaml:\"namespace\"`\n\t// Clone     bool            `yaml:\"clone\"`\n}\n\n// Src represents a source in the K8s test\ntype Src struct {\n\tK8sResource *K8sResource `yaml:\"k8sResource\"`\n}\n\n// Host represents a host that can be used as Dst in a K8s test\ntype Host struct {\n\tName string `yaml:\"name\"`\n}\n\n// Dst holds the destination or the target resource of the test\ntype Dst struct {\n\tK8sResource *K8sResource `yaml:\"k8sResource,omitempty\"`\n\tHost        *Host        `yaml:\"host,omitempty\"`\n}\n\n// Test holds a single netAssert test\ntype Test struct {\n\tName           string   `yaml:\"name\"`\n\tType           TestType `yaml:\"type\"`\n\tProtocol       Protocol `yaml:\"protocol\"`\n\tTargetPort     int      `yaml:\"targetPort\"`\n\tTimeoutSeconds int      `yaml:\"timeoutSeconds\"`\n\tAttempts       int      `yaml:\"attempts\"`\n\tExitCode       int      `yaml:\"exitCode\"`\n\tSrc            *Src     `yaml:\"src\"`\n\tDst            *Dst     `yaml:\"dst\"`\n\tPass           bool     `yaml:\"pass\"`\n\tFailureReason  string   `yaml:\"failureReason\"`\n}\n\n// Tests - holds a slice of NetAssertTests\ntype Tests []*Test\n\nfunc (r *K8sResource) validate() error {\n\tif r == nil {\n\t\treturn fmt.Errorf(\"K8sResource is empty\")\n\t}\n\n\tvar (\n\t\tnameErr         error\n\t\tkindErr         error\n\t\tnameSpaceErr    error\n\t\tresourceKindErr error\n\t)\n\n\tif r.Name == \"\" {\n\t\tnameErr = fmt.Errorf(\"k8sResource name is missing\")\n\t}\n\n\tif r.Kind == \"\" {\n\t\tkindErr = fmt.Errorf(\"k8sResource kind is missing\")\n\t}\n\n\tif r.Namespace == \"\" {\n\t\tnameSpaceErr = fmt.Errorf(\"k8sResource namespace is missing\")\n\t}\n\n\tif _, ok := ValidK8sResourceKinds[r.Kind]; !ok {\n\t\tresourceKindErr = fmt.Errorf(\"k8sResource invalid kind '%s'\", r.Kind)\n\t}\n\n\treturn errors.Join(nameErr, kindErr, nameSpaceErr, resourceKindErr)\n}\n\n// validate - validates the Host type\nfunc (h *Host) validate() error {\n\tif h == nil {\n\t\treturn fmt.Errorf(\"host field is nil\")\n\t}\n\n\tif h.Name == \"\" {\n\t\treturn fmt.Errorf(\"host field is set to empty string\")\n\t}\n\n\treturn nil\n}\n\n// validate - validates the Dst type\nfunc (d *Dst) validate() error {\n\tif d == nil {\n\t\treturn fmt.Errorf(\"dst field cannot be nil\")\n\t}\n\n\tif d.K8sResource != nil && d.Host != nil {\n\t\treturn fmt.Errorf(\"dst field only supports K8sResource or Host but not both\")\n\t}\n\n\tif d.K8sResource != nil {\n\t\treturn d.K8sResource.validate()\n\t}\n\n\tif d.Host != nil {\n\t\treturn d.Host.validate()\n\t}\n\n\treturn nil\n}\n\n// validate - validates the Src type\nfunc (d *Src) validate() error {\n\tif d == nil {\n\t\treturn fmt.Errorf(\"src field cannot be nil\")\n\t}\n\n\tif d.K8sResource == nil {\n\t\treturn fmt.Errorf(\"k8sResource field in src is currently the only source allowed\")\n\t}\n\n\treturn d.K8sResource.validate()\n}\n\n// validate - validates the Test case\nfunc (te *Test) validate() error {\n\tif te == nil {\n\t\treturn fmt.Errorf(\"test is pointing to nil\")\n\t}\n\n\tvar nameErr error\n\tif te.Name == \"\" {\n\t\tnameErr = fmt.Errorf(\"name field is missing\")\n\t}\n\n\tvar invalidProtocolErr error\n\tif te.Protocol != ProtocolUDP && te.Protocol != ProtocolTCP {\n\t\tinvalidProtocolErr = fmt.Errorf(\"invalid protocol %s\", te.Protocol)\n\t}\n\n\tvar targetPortErr error\n\tif te.TargetPort < 1 || te.TargetPort > 65535 {\n\t\ttargetPortErr = fmt.Errorf(\"targetPort out of range: %d\", te.TargetPort)\n\t}\n\n\tvar invalidAttemptsErr error\n\tif te.Attempts < 1 {\n\t\tinvalidAttemptsErr = fmt.Errorf(\"attempts must be > 0\")\n\t}\n\n\tvar timeoutSecondsErr error\n\tif te.TimeoutSeconds < 1 {\n\t\ttimeoutSecondsErr = fmt.Errorf(\"timeoutSeconds must be > 0\")\n\t}\n\n\tvar invalidTestTypeErr error\n\tif _, ok := TestTypes[te.Type]; !ok {\n\t\tinvalidTestTypeErr = fmt.Errorf(\"invalid test type %v\", te.Type)\n\t}\n\n\tvar missingSrcErr, k8sResourceErr error\n\tif te.Src == nil {\n\t\tmissingSrcErr = fmt.Errorf(\"src block must be present\")\n\t} else {\n\t\tk8sResourceErr = te.Src.validate()\n\t}\n\n\tvar missingDstErr, dstValidationErr error\n\tif te.Dst == nil {\n\t\tmissingDstErr = fmt.Errorf(\"dst block must be present\")\n\t} else {\n\t\tdstValidationErr = te.Dst.validate()\n\t}\n\n\tvar notSupportedTest error\n\tif te.Protocol == ProtocolUDP && te.Dst != nil && te.Dst.Host != nil {\n\t\tnotSupportedTest = fmt.Errorf(\"with udp tests the destination must be a k8sResource\")\n\t}\n\n\treturn errors.Join(nameErr, invalidProtocolErr, targetPortErr,\n\t\tinvalidAttemptsErr, timeoutSecondsErr, invalidTestTypeErr, k8sResourceErr,\n\t\tdstValidationErr, missingSrcErr, missingDstErr, notSupportedTest)\n}\n\n// Validate - validates the Tests type\nfunc (ts *Tests) Validate() error {\n\ttestNameMap := make(map[string]struct{})\n\n\tfor _, test := range *ts {\n\t\tif err := test.validate(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// if test name already exists\n\t\tif _, ok := testNameMap[test.Name]; ok {\n\t\t\treturn fmt.Errorf(\"duplicate test name found %q\", test.Name)\n\t\t}\n\n\t\ttestNameMap[test.Name] = struct{}{}\n\t}\n\n\treturn nil\n}\n\n// setDefaults - sets sensible defaults to the Test\nfunc (te *Test) setDefaults() {\n\tif te.TimeoutSeconds == 0 {\n\t\tte.TimeoutSeconds = 15\n\t}\n\n\tif te.Attempts == 0 {\n\t\tte.Attempts = 3\n\t}\n\n\tif te.Protocol == \"\" {\n\t\tte.Protocol = ProtocolTCP\n\t}\n}\n\n// UnmarshalYAML - decodes Tests type\nfunc (ts *Tests) UnmarshalYAML(node *yaml.Node) error {\n\ttype tmpTests []*Test\n\tvar tmp tmpTests\n\n\tif err := node.Decode(&tmp); err != nil {\n\t\treturn err\n\t}\n\n\t*ts = Tests(tmp)\n\tif err := ts.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"validation failed for tests: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// NewFromReader - creates a new Test from an io.Reader\nfunc NewFromReader(r io.Reader) (Tests, error) {\n\tbuf, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot read from reader: %w\", err)\n\t}\n\n\tvar tests Tests\n\n\tif err := yaml.Unmarshal(buf, &tests); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal tests: %w\", err)\n\t}\n\n\tif len(tests) == 0 {\n\t\treturn Tests{}, nil\n\t}\n\n\treturn tests, nil\n}\n\n// UnmarshalYAML - decodes and validate Test type\nfunc (te *Test) UnmarshalYAML(node *yaml.Node) error {\n\t// testAlias is an alias to type Test\n\t// this is need to prevent recursive decoding\n\ttype testAlias Test\n\tvar ta testAlias\n\n\tif err := node.Decode(&ta); err != nil {\n\t\treturn err\n\t}\n\n\t// we need to type cast ta back to p to call the original\n\t// methods on that type to validate the Test\n\tp := Test(ta)\n\tp.setDefaults()\n\tif err := p.validate(); err != nil {\n\t\treturn err\n\t}\n\n\t// we need to ensure that te points to the modified type\n\t*te = p\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/data/types_test.go",
    "content": "package data\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewFromReader(t *testing.T) {\n\tt.Parallel()\n\ttests := map[string]struct {\n\t\tconfFile       string\n\t\twant           Tests\n\t\twantErrMatches []string\n\t}{\n\t\t\"empty\": {\n\t\t\tconfFile: \"empty.yaml\",\n\t\t\twant:     Tests{},\n\t\t},\n\t\t\"not a list\": {\n\t\t\tconfFile:       \"not-a-list.yaml\",\n\t\t\twantErrMatches: []string{\"cannot unmarshal\"},\n\t\t},\n\t\t\"wrong test values\": {\n\t\t\tconfFile: \"wrong-test-values.yaml\",\n\t\t\twantErrMatches: []string{\n\t\t\t\t\"invalid protocol\",\n\t\t\t\t\"targetPort out of range\",\n\t\t\t\t\"attempts must\",\n\t\t\t\t\"timeoutSeconds must\",\n\t\t\t\t\"k8sResource invalid kind\",\n\t\t\t\t\"host field is set to empty string\",\n\t\t\t},\n\t\t},\n\t\t\"missing fields\": {\n\t\t\tconfFile: \"missing-fields.yaml\",\n\t\t\twantErrMatches: []string{\n\t\t\t\t\"name field is missing\",\n\t\t\t\t\"targetPort out of range\",\n\t\t\t\t\"attempts must\",\n\t\t\t\t\"timeoutSeconds must\",\n\t\t\t\t\"invalid\",\n\t\t\t\t\"src is currently\",\n\t\t\t\t\"dst block must\",\n\t\t\t},\n\t\t},\n\t\t\"host as a source\": {\n\t\t\tconfFile:       \"host-as-source.yaml\",\n\t\t\twantErrMatches: []string{\"k8sResource field in src is currently the only source allowed\"},\n\t\t},\n\t\t\"multiple destination blocks\": {\n\t\t\tconfFile:       \"multiple-dst-blocks.yaml\",\n\t\t\twantErrMatches: []string{\"dst field only supports K8sResource or Host but not both\"},\n\t\t},\n\t\t\"duplicated test names\": {\n\t\t\tconfFile:       \"duplicated-names.yaml\",\n\t\t\twantErrMatches: []string{\"duplicate test name found\"},\n\t\t},\n\t\t\"empty resources\": {\n\t\t\tconfFile: \"empty-resources.yaml\",\n\t\t\twantErrMatches: []string{\n\t\t\t\t\"k8sResource name is missing\",\n\t\t\t\t\"k8sResource kind is missing\",\n\t\t\t\t\"k8sResource namespace is missing\",\n\t\t\t\t\"k8sResource invalid kind\",\n\t\t\t},\n\t\t},\n\t\t\"host as a destination with udp\": {\n\t\t\tconfFile:       \"host-as-dst-udp.yaml\",\n\t\t\twantErrMatches: []string{\"with udp tests the destination must be a k8sResource\"},\n\t\t},\n\t\t\"multi valid\": {\n\t\t\tconfFile: \"multi.yaml\",\n\t\t\twant: Tests{\n\t\t\t\t&Test{\n\t\t\t\t\tName:           \"testname\",\n\t\t\t\t\tType:           \"k8s\",\n\t\t\t\t\tProtocol:       ProtocolTCP,\n\t\t\t\t\tAttempts:       3,\n\t\t\t\t\tTimeoutSeconds: 15,\n\t\t\t\t\tTargetPort:     80,\n\t\t\t\t\tExitCode:       0,\n\t\t\t\t\tSrc: &Src{\n\t\t\t\t\t\tK8sResource: &K8sResource{\n\t\t\t\t\t\t\tName:      \"deployment1\",\n\t\t\t\t\t\t\tKind:      KindDeployment,\n\t\t\t\t\t\t\tNamespace: \"ns1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tDst: &Dst{\n\t\t\t\t\t\tHost: &Host{\n\t\t\t\t\t\t\tName: \"1.1.1.1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&Test{\n\t\t\t\t\tName:           \"testname2\",\n\t\t\t\t\tType:           \"k8s\",\n\t\t\t\t\tProtocol:       ProtocolUDP,\n\t\t\t\t\tAttempts:       20,\n\t\t\t\t\tTimeoutSeconds: 50,\n\t\t\t\t\tTargetPort:     8080,\n\t\t\t\t\tExitCode:       1,\n\t\t\t\t\tSrc: &Src{\n\t\t\t\t\t\tK8sResource: &K8sResource{\n\t\t\t\t\t\t\tName:      \"statefulset1\",\n\t\t\t\t\t\t\tKind:      KindStatefulSet,\n\t\t\t\t\t\t\tNamespace: \"ns1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tDst: &Dst{\n\t\t\t\t\t\tK8sResource: &K8sResource{\n\t\t\t\t\t\t\tName:      \"mypod\",\n\t\t\t\t\t\t\tKind:      KindPod,\n\t\t\t\t\t\t\tNamespace: \"ns2\",\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\tfor name, tt := range tests {\n\t\ttc := tt\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tvar testFile string\n\t\t\tif len(tc.wantErrMatches) > 0 {\n\t\t\t\ttestFile = filepath.Join(\"./testdata/invalid\", tc.confFile)\n\t\t\t} else {\n\t\t\t\ttestFile = filepath.Join(\"./testdata/valid\", tc.confFile)\n\t\t\t}\n\t\t\tc, err := os.Open(filepath.Clean(testFile))\n\t\t\trequire.NoError(t, err, \"cannot open test file %s, err: %v\", testFile, err)\n\n\t\t\tgot, err := NewFromReader(c)\n\n\t\t\terrc := c.Close()\n\t\t\trequire.NoError(t, errc, \"cannot close test file %s, err: %v\", testFile, errc)\n\n\t\t\trequire.NotEqualf(t, len(tc.wantErrMatches) > 0, err == nil, \"expecting an error: %v, got: %v\", tc.wantErrMatches, err)\n\t\t\tfor _, wem := range tc.wantErrMatches {\n\t\t\t\trequire.Equalf(t, strings.Contains(err.Error(), wem), true, \"expecting error to contain: %s, got: %v\", wem, err)\n\t\t\t}\n\n\t\t\trequire.Equalf(t, tc.want, got, \"expecting config %v, got: %v\", tc.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/engine/engine.go",
    "content": "//go:generate mockgen -destination=engine_mocks_test.go -package=engine github.com/controlplaneio/netassert/internal/engine NetAssertTestRunner\n\npackage engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\n// Engine - type responsible for running the netAssert test(s)\ntype Engine struct {\n\tService NetAssertTestRunner\n\tLog     hclog.Logger\n}\n\n// New - Returns a new instance of Engine\nfunc New(service NetAssertTestRunner, log hclog.Logger) *Engine {\n\treturn &Engine{Service: service, Log: log}\n}\n\n// GetPod - returns a running Pod defined by the K8sResource\nfunc (e *Engine) GetPod(ctx context.Context, res *data.K8sResource) (*corev1.Pod, error) {\n\tif res == nil {\n\t\treturn &corev1.Pod{}, fmt.Errorf(\"res parameter is nil\")\n\t}\n\n\tswitch res.Kind {\n\tcase data.KindDeployment:\n\t\treturn e.Service.GetPodInDeployment(ctx, res.Name, res.Namespace)\n\tcase data.KindStatefulSet:\n\t\treturn e.Service.GetPodInStatefulSet(ctx, res.Name, res.Namespace)\n\tcase data.KindDaemonSet:\n\t\treturn e.Service.GetPodInDaemonSet(ctx, res.Name, res.Namespace)\n\tcase data.KindPod:\n\t\treturn e.Service.GetPod(ctx, res.Name, res.Namespace)\n\tdefault:\n\t\te.Log.Error(\"\", hclog.Fmt(\"%s is not supported K8sResource\", res.Kind))\n\t\treturn &corev1.Pod{}, fmt.Errorf(\"%s is not supported K8sResource\", res.Kind)\n\t}\n}\n\n// RunTests - runs a list of net assert test cases\nfunc (e *Engine) RunTests(\n\tctx context.Context, // context information\n\tte data.Tests, // the list of tests we are running\n\tsnifferContainerPrefix string, // name of the sniffer container to use\n\tsnifferContainerImage string, // image location of the sniffer Container\n\tscannerContainerPrefix string, // name of the scanner container to use\n\tscannerContainerImage string, // image location of the scanner container\n\tsuffixLength int, // length of the random string that will be generated  and appended to the container name\n\tpause time.Duration, // time to pause before running a test\n\tpacketCaptureInterface string, // the network interface used to capture traffic by the sniffer container\n) {\n\tvar wg sync.WaitGroup\n\n\tfor i, tc := range te {\n\t\twg.Add(1)\n\t\tgo func(tc *data.Test, wg *sync.WaitGroup) {\n\t\t\tdefer wg.Done()\n\t\t\t// run the test case\n\t\t\terr := e.RunTest(ctx, tc, snifferContainerPrefix, snifferContainerImage,\n\t\t\t\tscannerContainerPrefix, scannerContainerImage, suffixLength, packetCaptureInterface)\n\t\t\tif err != nil {\n\t\t\t\te.Log.Error(\"Test execution failed\", \"Name\", tc.Name, \"error\", err)\n\t\t\t\ttc.FailureReason = err.Error()\n\t\t\t}\n\t\t}(tc, &wg)\n\n\t\tif i < len(te)-1 { // do not pause after the last test\n\t\t\tcancellableDelay(ctx, pause)\n\t\t}\n\t\t// If the context is cancelled, we need to break out of the loop\n\t\tif ctx.Err() != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\twg.Wait()\n}\n\n// cancellableDelay introduces a delay that can be interrupted by context cancellation.\n// This function is useful when you want to pause execution for a specific duration,\n// but also need the ability to respond quickly if an interrupt signal (like CTRL + C) is received.\nfunc cancellableDelay(ctx context.Context, duration time.Duration) {\n\tselect {\n\tcase <-time.After(duration):\n\t\t// The case of time.After(duration) is selected when the specified duration has elapsed.\n\t\t// This means the function completes its normal delay without any interruption.\n\n\tcase <-ctx.Done():\n\t\t// The ctx.Done() case is selected if the context is cancelled before the duration elapses.\n\t\t// This could happen if an interrupt signal is received.\n\t\t// Returning early from the function allows the program to quickly respond to the cancellation signal,\n\t\t// such as cleaning up resources, stopping further processing, etc.\n\n\t\t// No specific action is needed here other than returning from the function,\n\t\t// as the cancellation of the context is handled by the caller.\n\t\treturn\n\t}\n}\n\n// RunTest - Runs a single netAssert test case\nfunc (e *Engine) RunTest(\n\tctx context.Context, // context passed to this function\n\tte *data.Test, // test cases to execute\n\tsnifferContainerPrefix string, // name of the sniffer container to use\n\tsnifferContainerImage string, // image location of the sniffer Container\n\tscannerContainerPrefix string, // name of the scanner container to use\n\tscannerContainerImage string, // image location of the scanner container\n\tsuffixLength int, // length of string that will be generated and appended to the container name\n\tpacketCaptureInterface string, // the network interface used to capture traffic by the sniffer container\n) error {\n\tif te.Type != data.K8sTest {\n\t\treturn fmt.Errorf(\"only k8s test type is supported at this time: %s\", te.Type)\n\t}\n\n\tswitch te.Protocol {\n\tcase data.ProtocolTCP:\n\t\treturn e.RunTCPTest(ctx, te, scannerContainerPrefix, scannerContainerImage, suffixLength)\n\tcase data.ProtocolUDP:\n\t\treturn e.RunUDPTest(ctx,\n\t\t\tte,\n\t\t\tsnifferContainerPrefix,\n\t\t\tsnifferContainerImage,\n\t\t\tscannerContainerPrefix,\n\t\t\tscannerContainerImage,\n\t\t\tsuffixLength,\n\t\t\tpacketCaptureInterface,\n\t\t)\n\tdefault:\n\t\te.Log.Error(\"error\", hclog.Fmt(\"Only TCP/UDP protocol is supported at this time and not %s\", te.Protocol))\n\t\treturn fmt.Errorf(\"only TCP/UDP protocol is supported at this time and not %v\", te.Protocol)\n\t}\n}\n"
  },
  {
    "path": "internal/engine/engine_daemonset_test.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\nfunc TestEngine_GetPod_DaemonSet(t *testing.T) {\n\tmockCtrl := gomock.NewController(t)\n\tt.Cleanup(func() {\n\t\tmockCtrl.Finish()\n\t})\n\n\tvar (\n\t\tpodName       = \"foo-pod\"\n\t\tnamespace     = \"default\"\n\t\tdaemonSetName = \"foo-ds\"\n\t)\n\n\tt.Run(\"GetPod from DaemonSet when Pod exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindDaemonSet,\n\t\t\tName:      daemonSetName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPodInDaemonSet(ctx, daemonSetName, namespace).\n\t\t\tReturn(&corev1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      podName,\n\t\t\t\t\tNamespace: namespace,\n\t\t\t\t},\n\t\t\t\tSpec:   corev1.PodSpec{},\n\t\t\t\tStatus: corev1.PodStatus{},\n\t\t\t}, nil)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\tpod, err := eng.GetPod(ctx, &res)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, pod.Namespace, namespace)\n\t\trequire.Equal(t, pod.Name, podName)\n\t})\n\n\t//\n\tt.Run(\"GetPod from DaemonSet when Pod does not exist\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindDaemonSet,\n\t\t\tName:      daemonSetName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.\n\t\t\tEXPECT().\n\t\t\tGetPodInDaemonSet(ctx, daemonSetName, namespace).\n\t\t\tReturn(\n\t\t\t\t&corev1.Pod{},\n\t\t\t\tfmt.Errorf(\"pod not found\"),\n\t\t\t)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\t_, err := eng.GetPod(ctx, &res)\n\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "internal/engine/engine_deployment_test.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"go.uber.org/mock/gomock\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\nfunc TestEngine_GetPod_Deployment(t *testing.T) {\n\tmockCtrl := gomock.NewController(t)\n\tdefer mockCtrl.Finish()\n\n\tvar (\n\t\tpodName        = \"foo-pod\"\n\t\tnamespace      = \"default\"\n\t\tdeploymentName = \"foo-deploy\"\n\t)\n\n\tt.Run(\"test GetPod from deployment when Pod exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindDeployment,\n\t\t\tName:      deploymentName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPodInDeployment(ctx, deploymentName, namespace).\n\t\t\tReturn(&corev1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      podName,\n\t\t\t\t\tNamespace: namespace,\n\t\t\t\t},\n\t\t\t\tSpec:   corev1.PodSpec{},\n\t\t\t\tStatus: corev1.PodStatus{},\n\t\t\t}, nil)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\tpod, err := eng.GetPod(ctx, &res)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, pod.Namespace, namespace)\n\t\trequire.Equal(t, pod.Name, podName)\n\t})\n\n\tt.Run(\"test GetPod from deployment when Pod does not exist\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindDeployment,\n\t\t\tName:      deploymentName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.\n\t\t\tEXPECT().\n\t\t\tGetPodInDeployment(ctx, deploymentName, namespace).\n\t\t\tReturn(\n\t\t\t\t&corev1.Pod{},\n\t\t\t\tfmt.Errorf(\"pod not found\"),\n\t\t\t)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\t_, err := eng.GetPod(ctx, &res)\n\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "internal/engine/engine_mocks_test.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/controlplaneio/netassert/internal/engine (interfaces: NetAssertTestRunner)\n\n// Package engine is a generated GoMock package.\npackage engine\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\ttime \"time\"\n\n\tgomock \"go.uber.org/mock/gomock\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\n// MockNetAssertTestRunner is a mock of NetAssertTestRunner interface.\ntype MockNetAssertTestRunner struct {\n\tctrl     *gomock.Controller\n\trecorder *MockNetAssertTestRunnerMockRecorder\n}\n\n// MockNetAssertTestRunnerMockRecorder is the mock recorder for MockNetAssertTestRunner.\ntype MockNetAssertTestRunnerMockRecorder struct {\n\tmock *MockNetAssertTestRunner\n}\n\n// NewMockNetAssertTestRunner creates a new mock instance.\nfunc NewMockNetAssertTestRunner(ctrl *gomock.Controller) *MockNetAssertTestRunner {\n\tmock := &MockNetAssertTestRunner{ctrl: ctrl}\n\tmock.recorder = &MockNetAssertTestRunnerMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use.\nfunc (m *MockNetAssertTestRunner) EXPECT() *MockNetAssertTestRunnerMockRecorder {\n\treturn m.recorder\n}\n\n// BuildEphemeralScannerContainer mocks base method.\nfunc (m *MockNetAssertTestRunner) BuildEphemeralScannerContainer(arg0, arg1, arg2, arg3, arg4, arg5 string, arg6 int) (*v1.EphemeralContainer, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BuildEphemeralScannerContainer\", arg0, arg1, arg2, arg3, arg4, arg5, arg6)\n\tret0, _ := ret[0].(*v1.EphemeralContainer)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BuildEphemeralScannerContainer indicates an expected call of BuildEphemeralScannerContainer.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) BuildEphemeralScannerContainer(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BuildEphemeralScannerContainer\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).BuildEphemeralScannerContainer), arg0, arg1, arg2, arg3, arg4, arg5, arg6)\n}\n\n// BuildEphemeralSnifferContainer mocks base method.\nfunc (m *MockNetAssertTestRunner) BuildEphemeralSnifferContainer(arg0, arg1, arg2 string, arg3 int, arg4 string, arg5 int, arg6 string, arg7 int) (*v1.EphemeralContainer, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"BuildEphemeralSnifferContainer\", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)\n\tret0, _ := ret[0].(*v1.EphemeralContainer)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// BuildEphemeralSnifferContainer indicates an expected call of BuildEphemeralSnifferContainer.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) BuildEphemeralSnifferContainer(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"BuildEphemeralSnifferContainer\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).BuildEphemeralSnifferContainer), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)\n}\n\n// GetExitStatusOfEphemeralContainer mocks base method.\nfunc (m *MockNetAssertTestRunner) GetExitStatusOfEphemeralContainer(arg0 context.Context, arg1 string, arg2 time.Duration, arg3, arg4 string) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetExitStatusOfEphemeralContainer\", arg0, arg1, arg2, arg3, arg4)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetExitStatusOfEphemeralContainer indicates an expected call of GetExitStatusOfEphemeralContainer.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) GetExitStatusOfEphemeralContainer(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetExitStatusOfEphemeralContainer\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).GetExitStatusOfEphemeralContainer), arg0, arg1, arg2, arg3, arg4)\n}\n\n// GetPod mocks base method.\nfunc (m *MockNetAssertTestRunner) GetPod(arg0 context.Context, arg1, arg2 string) (*v1.Pod, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPod\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*v1.Pod)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetPod indicates an expected call of GetPod.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) GetPod(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPod\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).GetPod), arg0, arg1, arg2)\n}\n\n// GetPodInDaemonSet mocks base method.\nfunc (m *MockNetAssertTestRunner) GetPodInDaemonSet(arg0 context.Context, arg1, arg2 string) (*v1.Pod, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPodInDaemonSet\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*v1.Pod)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetPodInDaemonSet indicates an expected call of GetPodInDaemonSet.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) GetPodInDaemonSet(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPodInDaemonSet\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).GetPodInDaemonSet), arg0, arg1, arg2)\n}\n\n// GetPodInDeployment mocks base method.\nfunc (m *MockNetAssertTestRunner) GetPodInDeployment(arg0 context.Context, arg1, arg2 string) (*v1.Pod, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPodInDeployment\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*v1.Pod)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetPodInDeployment indicates an expected call of GetPodInDeployment.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) GetPodInDeployment(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPodInDeployment\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).GetPodInDeployment), arg0, arg1, arg2)\n}\n\n// GetPodInStatefulSet mocks base method.\nfunc (m *MockNetAssertTestRunner) GetPodInStatefulSet(arg0 context.Context, arg1, arg2 string) (*v1.Pod, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetPodInStatefulSet\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*v1.Pod)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// GetPodInStatefulSet indicates an expected call of GetPodInStatefulSet.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) GetPodInStatefulSet(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetPodInStatefulSet\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).GetPodInStatefulSet), arg0, arg1, arg2)\n}\n\n// LaunchEphemeralContainerInPod mocks base method.\nfunc (m *MockNetAssertTestRunner) LaunchEphemeralContainerInPod(arg0 context.Context, arg1 *v1.Pod, arg2 *v1.EphemeralContainer) (*v1.Pod, string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"LaunchEphemeralContainerInPod\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(*v1.Pod)\n\tret1, _ := ret[1].(string)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// LaunchEphemeralContainerInPod indicates an expected call of LaunchEphemeralContainerInPod.\nfunc (mr *MockNetAssertTestRunnerMockRecorder) LaunchEphemeralContainerInPod(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LaunchEphemeralContainerInPod\", reflect.TypeOf((*MockNetAssertTestRunner)(nil).LaunchEphemeralContainerInPod), arg0, arg1, arg2)\n}\n"
  },
  {
    "path": "internal/engine/engine_pod_test.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\nfunc TestEngine_GetPod_Pod(t *testing.T) {\n\tmockCtrl := gomock.NewController(t)\n\tdefer mockCtrl.Finish()\n\n\tvar (\n\t\tpodName   = \"foo-pod\"\n\t\tnamespace = \"default\"\n\t)\n\n\tt.Run(\"test GetPod from statefulSet when Pod exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindPod,\n\t\t\tName:      podName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPod(ctx, podName, namespace).\n\t\t\tReturn(&corev1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      podName,\n\t\t\t\t\tNamespace: namespace,\n\t\t\t\t},\n\t\t\t\tSpec:   corev1.PodSpec{},\n\t\t\t\tStatus: corev1.PodStatus{},\n\t\t\t}, nil)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\tpod, err := eng.GetPod(ctx, &res)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, pod.Namespace, namespace)\n\t\trequire.Equal(t, pod.Name, podName)\n\t})\n\n\tt.Run(\"GetPod when Pod does not exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindPod,\n\t\t\tName:      podName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPod(ctx, podName, namespace).\n\t\t\tReturn(&corev1.Pod{}, fmt.Errorf(\"pod does not exist\"))\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\t_, err := eng.GetPod(ctx, &res)\n\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "internal/engine/engine_statefulset_test.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"go.uber.org/mock/gomock\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\nfunc TestEngine_GetPod_StatefulSet(t *testing.T) {\n\tmockCtrl := gomock.NewController(t)\n\tt.Cleanup(func() { mockCtrl.Finish() })\n\n\tvar (\n\t\tpodName         = \"foo-pod\"\n\t\tnamespace       = \"default\"\n\t\tstatefulSetName = \"foo-statefulset\"\n\t)\n\n\tt.Run(\"test GetPod from statefulSet when Pod exists\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindStatefulSet,\n\t\t\tName:      statefulSetName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPodInStatefulSet(ctx, statefulSetName, namespace).\n\t\t\tReturn(&corev1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      podName,\n\t\t\t\t\tNamespace: namespace,\n\t\t\t\t},\n\t\t\t\tSpec:   corev1.PodSpec{},\n\t\t\t\tStatus: corev1.PodStatus{},\n\t\t\t}, nil)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\tpod, err := eng.GetPod(ctx, &res)\n\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, pod.Namespace, namespace)\n\t\trequire.Equal(t, pod.Name, podName)\n\t})\n\n\tt.Run(\"test GetPod from statefulSet when Pod does not exist\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tctx := context.Background()\n\t\tres := data.K8sResource{\n\t\t\tKind:      data.KindStatefulSet,\n\t\t\tName:      statefulSetName,\n\t\t\tNamespace: namespace,\n\t\t}\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.\n\t\t\tEXPECT().\n\t\t\tGetPodInStatefulSet(ctx, statefulSetName, namespace).\n\t\t\tReturn(\n\t\t\t\t&corev1.Pod{},\n\t\t\t\tfmt.Errorf(\"pod not found\"),\n\t\t\t)\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\t_, err := eng.GetPod(ctx, &res)\n\n\t\trequire.Error(t, err)\n\t})\n}\n"
  },
  {
    "path": "internal/engine/interface.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// PodGetter - gets a running Pod from various kubernetes resources\ntype PodGetter interface {\n\tGetPodInDaemonSet(context.Context, string, string) (*corev1.Pod, error)\n\tGetPodInDeployment(context.Context, string, string) (*corev1.Pod, error)\n\tGetPodInStatefulSet(context.Context, string, string) (*corev1.Pod, error)\n\tGetPod(context.Context, string, string) (*corev1.Pod, error)\n}\n\n// EphemeralContainerOperator - various operations related to the ephemeral container(s)\ntype EphemeralContainerOperator interface {\n\tBuildEphemeralScannerContainer(\n\t\tname string, // name of the ephemeral container\n\t\timage string, // image location of the container\n\t\ttargetHost string, // host to connect to\n\t\ttargetPort string, // target Port to connect to\n\t\tprotocol string, // protocol to used for connection\n\t\tmessage string, // message to pass to the remote target\n\t\tattempts int, // Number of attempts\n\t) (*corev1.EphemeralContainer, error)\n\n\tGetExitStatusOfEphemeralContainer(\n\t\tctx context.Context, // context passed to the function\n\t\tcontainerName string, // name of the ephemeral container\n\t\ttimeOut time.Duration, // maximum duration to poll for the ephemeral container status\n\t\tpodName string, // name of the pod that houses the ephemeral container\n\t\tpodNamespace string, // namespace of the pod that houses the ephemeral container\n\t) (int, error)\n\n\tBuildEphemeralSnifferContainer(\n\t\tname string, // name of the ephemeral container\n\t\timage string, // image location of the container\n\t\tsearch string, // search for this string in the captured packet\n\t\tsnapLen int, // snapLength to capture\n\t\tprotocol string, // protocol to capture\n\t\tnumberOfmatches int, // no. of matches\n\t\tintFace string, // the network interface to read the packets from\n\t\ttimeoutSec int, // timeout for the ephemeral container in seconds\n\t) (*corev1.EphemeralContainer, error)\n\n\tLaunchEphemeralContainerInPod(\n\t\tctx context.Context, // the context\n\t\tpod *corev1.Pod, // the target Pod\n\t\tec *corev1.EphemeralContainer, // the ephemeralContainer that needs to be injected to the Pod\n\t) (*corev1.Pod, string, error)\n}\n\n// NetAssertTestRunner - runs netassert test case(s)\ntype NetAssertTestRunner interface {\n\tEphemeralContainerOperator\n\tPodGetter\n}\n"
  },
  {
    "path": "internal/engine/run_tcp.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n\t\"github.com/controlplaneio/netassert/v2/internal/kubeops\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// RunTCPTest - runs a TCP test\nfunc (e *Engine) RunTCPTest(\n\tctx context.Context, // context information\n\tte *data.Test, // test case we want to run\n\tscannerContainerName string, // name of the scanner container to use\n\tscannerContainerImage string, // docker image location of the scanner container image\n\tsuffixLength int, // length of random string that will be appended to the ephemeral container name\n) error {\n\tvar (\n\t\tdstPod     *corev1.Pod\n\t\ttargetHost string\n\t)\n\n\tif te.Dst.Host != nil && te.Dst.K8sResource != nil {\n\t\treturn fmt.Errorf(\"both Dst.Host and Dst.K8sResource cannot be set at the same time\")\n\t}\n\n\tif te.Dst.Host == nil && te.Dst.K8sResource == nil {\n\t\treturn fmt.Errorf(\"both Dst.Host and Dst.K8sResource are nil\")\n\t}\n\n\tif scannerContainerName == \"\" {\n\t\treturn fmt.Errorf(\"scannerContainerName parameter cannot be empty string\")\n\t}\n\n\tif scannerContainerImage == \"\" {\n\t\treturn fmt.Errorf(\"scannerContainerImage parameter cannot be empty string\")\n\t}\n\n\te.Log.Info(\"🟢 Running TCP test\", \"Name\", te.Name)\n\n\tsrcPod, err := e.GetPod(ctx, te.Src.K8sResource)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// if Destination K8sResource is not set to nil\n\tif te.Dst.K8sResource != nil {\n\t\t// we need to find a running Pod  with IP Address in the Dst K8sResource\n\t\tdstPod, err = e.GetPod(ctx, te.Dst.K8sResource)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttargetHost = dstPod.Status.PodIP\n\t} else {\n\t\ttargetHost = te.Dst.Host.Name\n\t}\n\n\t// build ephemeral container with details of the IP addresses\n\tmsg, err := kubeops.NewUUIDString()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to genereate random UUID for test %s: %w\", te.Name, err)\n\t}\n\n\tdebugContainer, err := e.Service.BuildEphemeralScannerContainer(\n\t\tscannerContainerName+\"-\"+kubeops.RandString(suffixLength),\n\t\tscannerContainerImage,\n\t\ttargetHost,\n\t\tstrconv.Itoa(te.TargetPort),\n\t\tstring(te.Protocol),\n\t\tmsg,\n\t\tte.Attempts,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to build ephemeral scanner container for test %s: %w\", te.Name, err)\n\t}\n\n\t// run the ephemeral/debug container\n\t// grab the exit code\n\t// make sure that the exit code matches the one that is specified in the test\n\tsrcPod, ephContainerName, err := e.Service.LaunchEphemeralContainerInPod(ctx, srcPod, debugContainer)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"ephemeral container launch failed for test %s: %w\", te.Name, err)\n\t}\n\n\terr = e.CheckExitStatusOfEphContainer(\n\t\tctx,\n\t\tephContainerName,\n\t\tte.Name,\n\t\tsrcPod.Name,\n\t\tsrcPod.Namespace,\n\t\ttime.Duration(te.TimeoutSeconds)*time.Second,\n\t\tte.ExitCode,\n\t)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tte.Pass = true // set the test as pass\n\treturn nil\n}\n\n// CheckExitStatusOfEphContainer - returns an error if exit code of the ephemeral container does not match expExitCode\nfunc (e *Engine) CheckExitStatusOfEphContainer(\n\tctx context.Context, // context to pass to our function\n\tephContainerName string, // name of the ephemeral container\n\ttestCaseName string, // name of the test case\n\tpodName string, // name of the pod that houses the ephemeral container\n\tpodNamespace string, // namespace of the pod that houses the ephemeral container\n\ttimeout time.Duration, // timeout for the exit status to reach the desired exit code\n\texpExitCode int, // expected exit code from the ephemeral container\n) error {\n\tcontainerExitCode, err := e.Service.GetExitStatusOfEphemeralContainer(\n\t\tctx,\n\t\tephContainerName,\n\t\ttimeout,\n\t\tpodName,\n\t\tpodNamespace,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get exit code of the ephemeral container %s for test %s: %w\",\n\t\t\tephContainerName, testCaseName, err)\n\t}\n\n\te.Log.Info(\"Got exit code from ephemeral container\",\n\t\t\"testName\", testCaseName,\n\t\t\"exitCode\", containerExitCode,\n\t\t\"container\", ephContainerName,\n\t)\n\n\tif containerExitCode != expExitCode {\n\t\te.Log.Error(\"Got exit code from ephemeral container\",\n\t\t\t\"testName\", testCaseName,\n\t\t\t\"exitCode\", containerExitCode,\n\t\t\t\"expectedExitCode\", expExitCode,\n\t\t\t\"container\", ephContainerName,\n\t\t)\n\t\treturn fmt.Errorf(\"ephemeral container %s exit code for test %v is %v instead of %v\",\n\t\t\tephContainerName, testCaseName, containerExitCode, expExitCode)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/engine/run_tcp_test.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/mock/gomock\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n)\n\nvar sampleTest = `\n- name: busybox-deploy-to-echoserver-deploy\n  type: k8s\n  protocol: tcp\n  targetPort: 8080\n  timeoutSeconds: 20\n  attempts: 3\n  exitCode: 0\n  src:\n    k8sResource:\n      kind: deployment\n      name: busybox\n      namespace: busybox\n  dst:\n    k8sResource:\n      kind: deployment\n      name: echoserver\n      namespace: echoserver\n`\n\nfunc TestEngine_RunTCPTest(t *testing.T) {\n\tt.Run(\"error when BuildEphemeralScannerContainer fails\", func(t *testing.T) {\n\t\tr := require.New(t)\n\t\tctx := context.Background()\n\t\ttestCases, err := data.NewFromReader(strings.NewReader(sampleTest))\n\t\tr.Nil(err)\n\n\t\t// we only expect a single testCase to be present\n\n\t\tr.Equal(len(testCases), 1)\n\n\t\ttc := testCases[0]\n\t\tmockCtrl := gomock.NewController(t)\n\t\tdefer mockCtrl.Finish()\n\n\t\tmockRunner := NewMockNetAssertTestRunner(mockCtrl)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPodInDeployment(ctx, tc.Src.K8sResource.Name, tc.Src.K8sResource.Namespace).\n\t\t\tReturn(&corev1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"busybox\",\n\t\t\t\t\tNamespace: \"busybox\",\n\t\t\t\t},\n\t\t\t\tSpec:   corev1.PodSpec{},\n\t\t\t\tStatus: corev1.PodStatus{},\n\t\t\t}, nil)\n\n\t\tmockRunner.EXPECT().\n\t\t\tGetPodInDeployment(ctx, tc.Dst.K8sResource.Name, tc.Dst.K8sResource.Namespace).\n\t\t\tReturn(&corev1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"echoserver\",\n\t\t\t\t\tNamespace: \"echoserver\",\n\t\t\t\t},\n\t\t\t\tSpec:   corev1.PodSpec{},\n\t\t\t\tStatus: corev1.PodStatus{},\n\t\t\t}, nil)\n\n\t\tmockRunner.EXPECT().\n\t\t\tBuildEphemeralScannerContainer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),\n\t\t\t\tgomock.Any(), gomock.Any(), gomock.Any()).\n\t\t\tReturn(&corev1.EphemeralContainer{},\n\t\t\t\tfmt.Errorf(\"failed to build ephemeral scanner container\"))\n\n\t\teng := New(mockRunner, hclog.NewNullLogger())\n\n\t\terr = eng.RunTCPTest(ctx, testCases[0],\n\t\t\t\"scanner-container-name\",\n\t\t\t\"scanner-container-image\", 7)\n\n\t\tr.Error(err)\n\t\twantErrMsg := `unable to build ephemeral scanner container for test ` + tc.Name\n\t\tr.Contains(err.Error(), wantErrMsg)\n\t})\n}\n"
  },
  {
    "path": "internal/engine/run_udp.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/controlplaneio/netassert/v2/internal/data\"\n\t\"github.com/controlplaneio/netassert/v2/internal/kubeops\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\tdefaultNetInt                   = `eth0` // default network interface\n\tdefaultSnapLen                  = 1024   // default size of the packet snap length\n\tephemeralContainersExtraSeconds = 23     // fixed extra time given for the ephemeral containers to come online\n\tattemptsMultiplier              = 3      // increase the attempts to ensure that we send three times the packets\n)\n\n// RunUDPTest - runs a UDP test\nfunc (e *Engine) RunUDPTest(\n\tctx context.Context, // context information\n\tte *data.Test, // the test case to run\n\tsnifferContainerSuffix string, // name of the sniffer container to use\n\tsnifferContainerImage string, // image location of the sniffer Container\n\tscannerContainerSuffix string, // name of the scanner container to use\n\tscannerContainerImage string, // image location of the scanner container\n\tsuffixLength int, // length of string that will be generated and appended to the container name\n\tnetworkInterface string, // name of the network interface that will be used for packet capturing\n) error {\n\tif te == nil {\n\t\treturn fmt.Errorf(\"test case is nil object\")\n\t}\n\n\tif snifferContainerSuffix == \"\" {\n\t\treturn fmt.Errorf(\"snifferContainerSuffix parameter cannot be empty string\")\n\t}\n\n\tif snifferContainerImage == \"\" {\n\t\treturn fmt.Errorf(\"snifferContainerImage parameter cannot be empty string\")\n\t}\n\n\tif scannerContainerSuffix == \"\" {\n\t\treturn fmt.Errorf(\"scannerContainerSuffix parameter cannot be empty string\")\n\t}\n\n\tif scannerContainerImage == \"\" {\n\t\treturn fmt.Errorf(\"scannerContainerImage parameter cannot be empty string\")\n\t}\n\n\t// we only run UDP tests here\n\tif te.Protocol != data.ProtocolUDP {\n\t\treturn fmt.Errorf(\"test case protocol is set to %q, this function only supports %q\",\n\t\t\tte.Protocol, data.ProtocolUDP)\n\t}\n\n\t// te is already validate as validation is done at the Unmarshalling of the resource\n\t// validation ensures that for the time being the src holds a type of k8sResource\n\t// check if the te is not nil\n\t// check the value of te.Type and ensure that its k8s\n\t// check if the te.Protocol is tcp or udp\n\t// if the protocol is tcp\n\t// find a running Pod in the src resource\n\n\tvar (\n\t\tsrcPod, dstPod *corev1.Pod\n\t\ttargetHost     string\n\t\terr            error\n\t)\n\n\t// as we cannot inject ephemeral container when the Dst type is Host, we will\n\t// return an error\n\tif te.Dst.Host != nil {\n\t\treturn fmt.Errorf(\"%q: dst should not contain host object when protocol is %s\", te.Name, te.Protocol)\n\t}\n\n\tif te.Dst.K8sResource == nil {\n\t\treturn fmt.Errorf(\"%q: dst should contain non-nil k8sResource object\", te.Name)\n\t}\n\n\te.Log.Info(\"🟢 Running UDP test\", \"Name\", te.Name)\n\n\t// name of the network interface that will be used for packet capturing\n\t// if none is set then we used the default one i.e. eth0\n\tif networkInterface == \"\" {\n\t\tnetworkInterface = defaultNetInt\n\t}\n\n\t// find a running Pod represented by the  src.K8sResource object\n\tsrcPod, err = e.GetPod(ctx, te.Src.K8sResource)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to get source pod for test %s: %w\", te.Name, err)\n\t}\n\n\t// find a running Pod in the destination kubernetes object\n\tdstPod, err = e.GetPod(ctx, te.Dst.K8sResource)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttargetHost = dstPod.Status.PodIP\n\n\tmsg, err := kubeops.NewUUIDString()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to genereate random UUID for test %s: %w\", te.Name, err)\n\t}\n\n\t// we now have both the source and the destination object, we need to ensure that we first\n\t// inject the sniffer into the Destination Pod\n\tsnifferEphemeralContainer, err := e.Service.BuildEphemeralSnifferContainer(\n\t\tsnifferContainerSuffix+\"-\"+kubeops.RandString(suffixLength),\n\t\tsnifferContainerImage,\n\t\tmsg,\n\t\tdefaultSnapLen,\n\t\tstring(te.Protocol),\n\t\tte.Attempts,\n\t\tnetworkInterface,\n\t\tte.TimeoutSeconds,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to build sniffer ephemeral container for test %s: %w\", te.Name, err)\n\t}\n\n\t// we now build the scanner container\n\tscannerEphemeralContainer, err := e.Service.BuildEphemeralScannerContainer(\n\t\tscannerContainerSuffix+\"-\"+kubeops.RandString(suffixLength),\n\t\tscannerContainerImage,\n\t\ttargetHost,\n\t\tstrconv.Itoa(te.TargetPort),\n\t\tstring(te.Protocol),\n\t\tmsg,\n\t\tte.Attempts*attemptsMultiplier,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to build ephemeral scanner container for test %s: %w\", te.Name, err)\n\t}\n\n\t// run the ephemeral containers on dst Pod first and then the source Pod\n\tdstPod, snifferContainerName, err := e.Service.LaunchEphemeralContainerInPod(ctx, dstPod,\n\t\tsnifferEphemeralContainer)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"sniffer ephermal container launch failed for test %s: %w\", te.Name, err)\n\t}\n\n\t// run the ephemeral scanner container in the source Pod after we have\n\t// launched the sniffer and the sniffer container is ready\n\t_, scannerContainerName, err := e.Service.LaunchEphemeralContainerInPod(ctx, srcPod, scannerEphemeralContainer)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"scanner ephemeral container launch failed for test %s: %w\", te.Name, err)\n\t}\n\n\t// sniffer is successfully injected into the dstPod, now we check the exit code\n\texitCodeSnifferCtr, err := e.Service.GetExitStatusOfEphemeralContainer(\n\t\tctx,\n\t\tsnifferContainerName,\n\t\ttime.Duration(te.TimeoutSeconds+ephemeralContainersExtraSeconds)*time.Second,\n\t\tdstPod.Name,\n\t\tdstPod.Namespace,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get exit code of the sniffer ephemeral container %s for test %s: %w\",\n\t\t\tsnifferContainerName, te.Name, err)\n\t}\n\n\te.Log.Info(\"Got exit code from ephemeral sniffer container\",\n\t\t\"testName\", te.Name,\n\t\t\"exitCode\", exitCodeSnifferCtr,\n\t\t\"containerName\", snifferContainerName)\n\n\tif exitCodeSnifferCtr != te.ExitCode {\n\t\treturn fmt.Errorf(\"ephemeral sniffer container %s exit code for test %v is %v instead of %d\",\n\t\t\tsnifferContainerName, te.Name, exitCodeSnifferCtr, te.ExitCode)\n\t}\n\n\t// get the exit status of the scanner container\n\texitCodeScanner, err := e.Service.GetExitStatusOfEphemeralContainer(\n\t\tctx, scannerContainerName,\n\t\ttime.Duration(te.TimeoutSeconds+ephemeralContainersExtraSeconds)*time.Second,\n\t\tsrcPod.Name,\n\t\tsrcPod.Namespace,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get exit code of the scanner ephemeral container %s for test %s: %w\",\n\t\t\tscannerContainerName, te.Name, err)\n\t}\n\n\te.Log.Info(\"Got exit code from ephemeral scanner container\",\n\t\t\"testName\", te.Name,\n\t\t\"exitCode\", exitCodeScanner,\n\t\t\"containerName\", scannerContainerName)\n\n\t// for UDP scanning the exit code of the scanner is always zero\n\t// as UDP is connectionless\n\tif exitCodeScanner != 0 {\n\t\treturn fmt.Errorf(\"ephemeral scanner container %s exit code for test %v is %v instead of 0\",\n\t\t\tscannerContainerName, te.Name, exitCodeScanner)\n\t}\n\n\tte.Pass = true // mark test as pass\n\treturn nil\n}\n"
  },
  {
    "path": "internal/kubeops/client.go",
    "content": "package kubeops\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\n// generateKubernetsClient - generates a kubernetes client set from default file locations\n// first checks KUBECONFIG environment variable\n// then the Home directory of the user\n// then finally it checks if the program is running in a Pod\nfunc generateKubernetesClient() (kubernetes.Interface, error) {\n\tconfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(\n\t\tclientcmd.NewDefaultClientConfigLoadingRules(), nil,\n\t).ClientConfig()\n\tif err != nil {\n\t\tif restConfig, err := rest.InClusterConfig(); err == nil {\n\t\t\tconfig = restConfig\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"failed to build kubeconfig or InClusterConfig: %w\", err)\n\t\t}\n\t}\n\n\tk8sClient, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create kubernetes client: %w\", err)\n\t}\n\n\treturn k8sClient, nil\n}\n\n// genK8sClientFromKubeConfigFile - Generates kubernetes config file from user supplied kubeConfigPath file\nfunc genK8sClientFromKubeConfigFile(kubeConfigPath string) (kubernetes.Interface, error) {\n\tconfig, err := clientcmd.BuildConfigFromFlags(\"\", kubeConfigPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to build kubeconfig from file %s: %w\", kubeConfigPath, err)\n\t}\n\n\tk8sClient, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create kubernetes client from file %s: %w\", kubeConfigPath, err)\n\t}\n\n\treturn k8sClient, nil\n}\n\n// Service exposes the operations on various K8s resources\ntype Service struct {\n\tClient kubernetes.Interface // kubernetes client-set\n\tLog    hclog.Logger         // logger embedded in our service\n}\n\n// New - builds a new Service that can interface with Kubernetes\nfunc New(client kubernetes.Interface, l hclog.Logger) *Service {\n\treturn &Service{\n\t\tClient: client,\n\t\tLog:    l,\n\t}\n}\n\n// NewDefaultService - builds a new Service looking for a KubeConfig in various locations\nfunc NewDefaultService(l hclog.Logger) (*Service, error) {\n\tclientSet, err := generateKubernetesClient()\n\tif err != nil {\n\t\treturn &Service{}, err\n\t}\n\n\treturn &Service{\n\t\tClient: clientSet,\n\t\tLog:    l,\n\t}, nil\n}\n\n// NewServiceFromKubeConfigFile - builds a new Service using KubeConfig file location passed by the caller\nfunc NewServiceFromKubeConfigFile(kubeConfigPath string, l hclog.Logger) (*Service, error) {\n\tclientSet, err := genK8sClientFromKubeConfigFile(kubeConfigPath)\n\tif err != nil {\n\t\treturn &Service{}, err\n\t}\n\n\treturn &Service{\n\t\tClient: clientSet,\n\t\tLog:    l,\n\t}, nil\n}\n"
  },
  {
    "path": "internal/kubeops/container_test.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestLaunchEphemeralContainerInPod_InvalidEphemeralContainer(t *testing.T) {\n\t// create a fake pod with no ephemeral containers\n\tpod := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"fake-pod\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{\n\t\t\t\t{\n\t\t\t\t\tName:  \"nginx\",\n\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:          \"http\",\n\t\t\t\t\t\t\tContainerPort: 80,\n\t\t\t\t\t\t\tProtocol:      corev1.ProtocolTCP,\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\n\tctx := context.Background()\n\n\tfakeClient := fake.NewSimpleClientset(pod)\n\n\t// initialise our Service\n\tsvc := Service{\n\t\tClient: fakeClient,\n\t\tLog:    hclog.NewNullLogger(),\n\t}\n\n\tt.Run(\"Inject valid ephemeral container\", func(t *testing.T) {\n\t\tr := require.New(t)\n\n\t\tephContainerName := \"foo-container\"\n\n\t\t// create an invalid ephemeral container\n\t\tec, err := svc.BuildEphemeralSnifferContainer(\n\t\t\tephContainerName, // name of the ephemeral container\n\t\t\t\"foo:12\",         // image location of the container\n\t\t\t\"foo\",            // search for this string in the captured packet\n\t\t\t1024,             // snapLength to capture\n\t\t\t\"tcp\",            // protocol to capture\n\t\t\t3,                // no. of matches that triggers an exit with status 0\n\t\t\t\"eth0\",           // the network interface to read the packets from\n\t\t\t3,                // timeout for the ephemeral container\n\t\t)\n\n\t\tr.NoError(err, \"failed to Build Ephemeral Container \")\n\n\t\tpod, _, err := svc.LaunchEphemeralContainerInPod(ctx, pod, ec)\n\n\t\tr.NoError(err)\n\n\t\tgotName := pod.Spec.EphemeralContainers[0].EphemeralContainerCommon.Name\n\n\t\tr.Equal(ephContainerName, gotName)\n\n\t\tr.Equal(len(pod.Spec.EphemeralContainers), 1)\n\t})\n\n\tt.Run(\"inject valid ephemeral container in a Pod that does not exist\", func(t *testing.T) {\n\t\tr := require.New(t)\n\n\t\ttmpPod := &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"test-pod\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t}\n\n\t\t// create an invalid ephemeral container\n\t\tec, err := svc.BuildEphemeralSnifferContainer(\n\t\t\t\"eph1\",    // name of the ephemeral container\n\t\t\t\"foo:1.2\", // image location of the container\n\t\t\t\"foo\",     // search for this string in the captured packet\n\t\t\t1024,      // snapLength to capture\n\t\t\t\"tcp\",     // protocol to capture\n\t\t\t3,         // no. of matches that triggers an exit with status 0\n\t\t\t\"eth0\",    // the network interface to read the packets from\n\t\t\t3,         // timeout for the ephemeral container\n\t\t)\n\n\t\tr.NoError(err, \"failed to Build Ephemeral Container \")\n\n\t\tgotPod, _, err := svc.LaunchEphemeralContainerInPod(ctx, tmpPod, ec)\n\t\tr.Nil(gotPod)\n\t\tr.Error(err)\n\t\tr.Contains(err.Error(), `pods \"test-pod\" not found`)\n\t})\n}\n"
  },
  {
    "path": "internal/kubeops/containers.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/strategicpatch\"\n\t\"k8s.io/utils/pointer\"\n)\n\n// LaunchEphemeralContainerInPod - Launches an ephemeral container in running Pod\nfunc (svc *Service) LaunchEphemeralContainerInPod(\n\tctx context.Context, // the context\n\tpod *corev1.Pod, // the target Pod\n\tec *corev1.EphemeralContainer, // the ephemeralContainer that needs to be injected to the Pod\n) (*corev1.Pod, string, error) {\n\t// grab the JSON of the original Pod\n\toriginalPodJSON, err := json.Marshal(pod)\n\tif err != nil {\n\t\tsvc.Log.Error(\"Unable to marshal the original pod into JSON object\")\n\t\treturn nil, \"\", err\n\t}\n\n\tpodCopy := pod.DeepCopy()\n\n\t// Add the ephemeral container to the Pod spec of existing ephemeral containers\n\tpodCopy.Spec.EphemeralContainers = append(podCopy.Spec.EphemeralContainers, *ec)\n\n\tpodCopyJSON, err := json.Marshal(podCopy)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\tpatch, err := strategicpatch.CreateTwoWayMergePatch(originalPodJSON, podCopyJSON, pod)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\tsvc.Log.Debug(\"Generated JSON patch for the Pod\", \"JSONPatch\", string(patch))\n\tsvc.Log.Info(\"Patching Pod\", \"Pod\", pod.Name, \"Namespace\", pod.Namespace, \"Container\", ec.Name)\n\n\t// we now patch the Pod with ephemeral container\n\tnewPod, err := svc.Client.CoreV1().Pods(pod.Namespace).Patch(\n\t\tctx,\n\t\tpod.Name,\n\t\ttypes.StrategicMergePatchType,\n\t\tpatch,\n\t\tmetav1.PatchOptions{},\n\t\t\"ephemeralcontainers\",\n\t)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\treturn newPod, ec.Name, nil\n}\n\n// BuildEphemeralSnifferContainer - builds an ephemeral sniffer container\nfunc (svc *Service) BuildEphemeralSnifferContainer(\n\tname string, // name of the ephemeral container\n\timage string, // image location of the container\n\tsearch string, // search for this string in the captured packet\n\tsnapLen int, // snapLength to capture\n\tprotocol string, // protocol to capture\n\tnumberMatches int, // no. of matches that triggers an exit with status 0\n\tintFace string, // the network interface to read the packets from\n\ttimeoutSec int, // timeout for the ephemeral container\n) (*corev1.EphemeralContainer, error) {\n\tec := corev1.EphemeralContainer{\n\t\tEphemeralContainerCommon: corev1.EphemeralContainerCommon{\n\t\t\tName:  name,\n\t\t\tImage: image,\n\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t{\n\t\t\t\t\tName:  \"TIMEOUT_SECONDS\",\n\t\t\t\t\tValue: strconv.Itoa(timeoutSec),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"IFACE\",\n\t\t\t\t\tValue: intFace,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"SNAPLEN\",\n\t\t\t\t\tValue: strconv.Itoa(snapLen),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"SEARCH_STRING\",\n\t\t\t\t\tValue: search,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"PROTOCOL\",\n\t\t\t\t\tValue: protocol,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"MATCHES\",\n\t\t\t\t\tValue: strconv.Itoa(numberMatches),\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:     false,\n\t\t\tStdinOnce: false,\n\t\t\tTTY:       false,\n\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\tAdd: []corev1.Capability{\"NET_RAW\"},\n\t\t\t\t},\n\t\t\t\tAllowPrivilegeEscalation: pointer.Bool(false),\n\t\t\t\tRunAsNonRoot:             pointer.Bool(true),\n\t\t\t},\n\t\t},\n\t\t// empty string forces the container to run in the namespace of the Pod, rather than the container\n\t\t// this is default value, only added here for readability\n\t\tTargetContainerName: \"\",\n\t}\n\n\treturn &ec, nil\n}\n\n// BuildEphemeralScannerContainer - builds an ephemeral scanner container\nfunc (svc *Service) BuildEphemeralScannerContainer(\n\tname string, // name of the ephemeral container\n\timage string, // image location of the container\n\ttargetHost string, // host to connect to\n\ttargetPort string, // target Port to connect to\n\tprotocol string, // protocol to used for connection\n\tmessage string, // message to pass to the remote target\n\tattempts int, // Number of attempts\n) (*corev1.EphemeralContainer, error) {\n\tec := corev1.EphemeralContainer{\n\t\tEphemeralContainerCommon: corev1.EphemeralContainerCommon{\n\t\t\tName:  name,\n\t\t\tImage: image,\n\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t{\n\t\t\t\t\tName:  \"TARGET_HOST\",\n\t\t\t\t\tValue: targetHost,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"TARGET_PORT\",\n\t\t\t\t\tValue: targetPort,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"PROTOCOL\",\n\t\t\t\t\tValue: protocol,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"MESSAGE\",\n\t\t\t\t\tValue: message,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"ATTEMPTS\",\n\t\t\t\t\tValue: strconv.Itoa(attempts),\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:     false,\n\t\t\tStdinOnce: false,\n\t\t\tTTY:       false,\n\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\tRunAsNonRoot:             pointer.Bool(true),\n\t\t\t\tAllowPrivilegeEscalation: pointer.Bool(false),\n\t\t\t},\n\t\t},\n\t\t// empty string forces the container to run in the namespace of the Pod, rather than the container\n\t\t// this is default value, only added here for readability\n\t\tTargetContainerName: \"\",\n\t}\n\n\treturn &ec, nil\n}\n\n// GetExitStatusOfEphemeralContainer - returns the exit status of an EphemeralContainer in a pod\nfunc (svc *Service) GetExitStatusOfEphemeralContainer(\n\tctx context.Context, // the context\n\tcontainerName string, // name of the ephemeral container\n\ttimeOut time.Duration, // maximum duration to poll for the container status\n\tpodName string, // name of the pod which has the ephemeral container\n\tpodNamespace string, // namespace of the pod which has the ephemeral container\n) (int, error) {\n\t// we only want the Pods that are in running state\n\t// and are in specific namespace\n\tfieldSelector := fields.AndSelectors(\n\t\tfields.OneTermEqualSelector(\n\t\t\t\"status.phase\",\n\t\t\t\"Running\",\n\t\t),\n\t\tfields.OneTermEqualSelector(\n\t\t\t\"metadata.name\",\n\t\t\tpodName,\n\t\t),\n\t\tfields.OneTermEqualSelector(\n\t\t\t\"metadata.namespace\",\n\t\t\tpodNamespace,\n\t\t),\n\t)\n\n\tpodWatcher, err := svc.Client.CoreV1().Pods(podNamespace).Watch(ctx, metav1.ListOptions{\n\t\tTypeMeta:      metav1.TypeMeta{},\n\t\tFieldSelector: fieldSelector.String(),\n\t})\n\n\tdefer func() {\n\t\tif podWatcher != nil {\n\t\t\tpodWatcher.Stop()\n\t\t}\n\t}()\n\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\ttimer := time.NewTimer(timeOut)\n\tdefer func() {\n\t\tif ok := timer.Stop(); !ok {\n\t\t\tsvc.Log.Info(\"Unable to close the timer channel\")\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase event := <-podWatcher.ResultChan():\n\n\t\t\tpod, ok := event.Object.(*corev1.Pod)\n\t\t\t// if this is not a pod object, then we skip\n\t\t\tif !ok {\n\t\t\t\tbreak // breaks from the select\n\t\t\t}\n\n\t\t\tsvc.Log.Debug(\"Polling the status of ephemeral container\",\n\t\t\t\t\"pod\", pod.Name,\n\t\t\t\t\"namespace\", pod.Namespace,\n\t\t\t\t\"container\", containerName,\n\t\t\t)\n\n\t\t\tfor _, v := range pod.Status.EphemeralContainerStatuses {\n\n\t\t\t\tif v.Name != containerName {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif v.State.Waiting != nil {\n\t\t\t\t\tsvc.Log.Debug(\"Container state\", \"container\", containerName, \"state\", \"Waiting\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif v.State.Running != nil {\n\t\t\t\t\tsvc.Log.Debug(\"Container state\", \"container\", containerName, \"state\", \"Running\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif v.State.Terminated != nil {\n\t\t\t\t\tsvc.Log.Info(\"Ephemeral container has finished executing\", \"name\", containerName)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"ContainerName\", v.Name)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"ExitCode\", v.State.Terminated.ExitCode)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"ContainerID\", v.State.Terminated.ContainerID)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"FinishedAt\", v.State.Terminated.FinishedAt)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"StartedAt\", v.State.Terminated.StartedAt)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"Message\", v.State.Terminated.Message)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"Reason\", v.State.Terminated.Reason)\n\t\t\t\t\tsvc.Log.Debug(\"\", \"Signal\", v.State.Terminated.Signal)\n\t\t\t\t\treturn int(v.State.Terminated.ExitCode), nil\n\t\t\t\t}\n\n\t\t\t}\n\n\t\tcase <-timer.C:\n\t\t\treturn -1, fmt.Errorf(\"container %v did not reach termination state in %v seconds\", containerName, timeOut.Seconds())\n\t\tcase <-ctx.Done():\n\t\t\treturn -1, fmt.Errorf(\"process was cancelled: %w\", ctx.Err())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/kubeops/daemonset.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\n// GetPodInDaemonSet - Returns a running Pod with IP address in a DaemonSet\nfunc (svc *Service) GetPodInDaemonSet(ctx context.Context, name, namespace string) (*corev1.Pod, error) {\n\t// check if the DaemonSet actually exists\n\tds, err := svc.Client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif ds.Status.NumberAvailable == 0 {\n\t\tsvc.Log.Error(\"Found zero available Pods\", \"DaemonSet\", name, \"Namespace\", namespace)\n\t\treturn nil, fmt.Errorf(\"zero Pods are available in the daemonset %s\", name)\n\t}\n\n\tfieldSelector := fields.OneTermEqualSelector(\n\t\t\"status.phase\",\n\t\t\"Running\",\n\t).String()\n\n\t// Grab the labels from the DaemonSet selector\n\tpodLabels := labels.FormatLabels(ds.Spec.Selector.MatchLabels)\n\n\tpods, err := svc.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: podLabels,\n\t\tFieldSelector: fieldSelector,\n\t})\n\tif err != nil {\n\t\tsvc.Log.Error(\"Unable to list Pods\", \"namespace\", namespace, \"error\", err)\n\t\treturn nil, fmt.Errorf(\"unable to list Pods in namespace %s: %w\", namespace, err)\n\t}\n\n\tpod, err := getRandomPodFromPodList(ds, pods)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to find any Pod owned by daemonset %s in namespace %s\",\n\t\t\tname, namespace)\n\t}\n\n\treturn pod, nil\n}\n"
  },
  {
    "path": "internal/kubeops/daemonset_test.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/uuid\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc createDaemonSet(client kubernetes.Interface,\n\tname, namespace string,\n\tnumberAvailable int32,\n\tt *testing.T,\n) *appsv1.DaemonSet {\n\tds := &appsv1.DaemonSet{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t\tUID:       uuid.NewUUID(),\n\t\t},\n\t\tStatus: appsv1.DaemonSetStatus{\n\t\t\tNumberAvailable: numberAvailable,\n\t\t},\n\t\tSpec: appsv1.DaemonSetSpec{\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\"app\": \"foobar\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"foobar\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"nginx-ofcourse\",\n\t\t\t\t\t\t\tImage: \"nginx:latest\",\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\n\tctx := context.Background()\n\tobj, err := client.AppsV1().DaemonSets(namespace).Create(ctx, ds, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatal(\"failed to create daemon set\", err)\n\t}\n\n\treturn obj\n}\n\nfunc deaemonSetPod(\n\tclient kubernetes.Interface,\n\townerDaemonSet *appsv1.DaemonSet,\n\tpodName string,\n\tpodNamespace string,\n\tt *testing.T,\n\tpodPhase corev1.PodPhase,\n\tipAddress string,\n) *corev1.Pod {\n\tif ownerDaemonSet == nil {\n\t\tt.Fatal(\"ownerDaemonSet is set to nil\")\n\t}\n\n\t// Create the Pod with the appropriate owner reference\n\tpod := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      podName,\n\t\t\tNamespace: podNamespace,\n\t\t\tLabels:    ownerDaemonSet.Spec.Selector.MatchLabels,\n\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t*metav1.NewControllerRef(ownerDaemonSet, appsv1.SchemeGroupVersion.WithKind(\"DaemonSet\")),\n\t\t\t},\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{\n\t\t\t\t{\n\t\t\t\t\tName:  \"has-to-be-nginx\",\n\t\t\t\t\tImage: \"nginx:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tStatus: corev1.PodStatus{\n\t\t\tPhase: podPhase,\n\t\t\tPodIP: ipAddress,\n\t\t},\n\t}\n\n\t// Create the Pod in the Kubernetes cluster.\n\tpod, err := client.CoreV1().Pods(podNamespace).Create(\n\t\tcontext.Background(), pod, metav1.CreateOptions{},\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"unable to create pod: %v\", err)\n\t}\n\n\treturn pod\n}\n\nfunc TestGetPodInDaemonSet(t *testing.T) {\n\tt.Parallel()\n\ttestCases := []struct {\n\t\tname            string\n\t\tdsName          string // daemonSet name\n\t\tnamespace       string // namespace for both the Pod and daemonset\n\t\tcreatePod       bool\n\t\tcreateDS        bool // should the daemonset be created\n\t\tipAddress       string\n\t\tpodPhase        corev1.PodPhase\n\t\twantErr         bool\n\t\tnumberAvailable int32\n\t\terrMsg          string\n\t}{\n\t\t{\n\t\t\tname:            \"when both the DaemonSet and Pod do not exist\",\n\t\t\tdsName:          \"foo\",\n\t\t\tnamespace:       \"bar\",\n\t\t\tcreatePod:       false,\n\t\t\tcreateDS:        false,\n\t\t\tipAddress:       \"192.168.168.33\",\n\t\t\tpodPhase:        corev1.PodRunning,\n\t\t\twantErr:         true,\n\t\t\tnumberAvailable: 1,\n\t\t\terrMsg:          `daemonsets.apps \"foo\" not found`,\n\t\t},\n\t\t{\n\t\t\tname:            \"when the DaemonSet exists but the Pod does not\",\n\t\t\tdsName:          \"foo\",\n\t\t\tnamespace:       \"bar\",\n\t\t\tcreatePod:       false,\n\t\t\tcreateDS:        true,\n\t\t\tipAddress:       \"192.168.168.33\",\n\t\t\tpodPhase:        corev1.PodRunning,\n\t\t\twantErr:         true,\n\t\t\tnumberAvailable: 1,\n\t\t\terrMsg:          `unable to find any Pod`,\n\t\t},\n\t\t{\n\t\t\tname:            \"when the DaemonSet and Pod exists but the Pod is not in running state\",\n\t\t\tdsName:          \"foo\",\n\t\t\tnamespace:       \"bar\",\n\t\t\tcreatePod:       true,\n\t\t\tcreateDS:        true,\n\t\t\tipAddress:       \"192.168.168.33\",\n\t\t\tpodPhase:        corev1.PodPending,\n\t\t\twantErr:         true,\n\t\t\tnumberAvailable: 1,\n\t\t\terrMsg:          `unable to find any Pod`,\n\t\t},\n\t\t{\n\t\t\tname:            \"when the DaemonSet and Pod exists and the Pod is in running state\",\n\t\t\tdsName:          \"foo\",\n\t\t\tnamespace:       \"bar\",\n\t\t\tcreatePod:       true,\n\t\t\tcreateDS:        true,\n\t\t\tipAddress:       \"192.168.168.33\",\n\t\t\tpodPhase:        corev1.PodRunning,\n\t\t\twantErr:         false,\n\t\t\tnumberAvailable: 1,\n\t\t\terrMsg:          ``,\n\t\t},\n\t\t{\n\t\t\tname:            \"when the DaemonSet and Pod exists, the Pod is in running state but does not have valid IP address\",\n\t\t\tdsName:          \"foo\",\n\t\t\tnamespace:       \"bar\",\n\t\t\tcreatePod:       true,\n\t\t\tcreateDS:        true,\n\t\t\tipAddress:       \"\",\n\t\t\tpodPhase:        corev1.PodRunning,\n\t\t\twantErr:         true,\n\t\t\tnumberAvailable: 1,\n\t\t\terrMsg:          `unable to find any Pod owned by daemonset`,\n\t\t},\n\t\t{\n\t\t\tname:            \"when the DaemonSet exists, but has Status.NumberAvailable set to zero\",\n\t\t\tdsName:          \"foo\",\n\t\t\tnamespace:       \"bar\",\n\t\t\tcreatePod:       false,\n\t\t\tcreateDS:        true,\n\t\t\tipAddress:       \"\",\n\t\t\tpodPhase:        corev1.PodRunning,\n\t\t\twantErr:         true,\n\t\t\tnumberAvailable: 0,\n\t\t\terrMsg:          `zero Pods are available in the daemonset`,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\ttc := testCase\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tr := require.New(t)\n\n\t\t\tk8sClient := fake.NewSimpleClientset()\n\n\t\t\tvar ds *appsv1.DaemonSet // daemon set\n\n\t\t\tif tc.createDS {\n\t\t\t\ttmpDs := createDaemonSet(k8sClient, tc.dsName, tc.namespace, tc.numberAvailable, t)\n\t\t\t\tr.NotNil(tmpDs)\n\t\t\t\tds = tmpDs\n\t\t\t}\n\n\t\t\tif tc.createPod {\n\t\t\t\t_ = deaemonSetPod(k8sClient, ds, \"foo-pod\", tc.namespace, t, tc.podPhase, tc.ipAddress)\n\t\t\t}\n\n\t\t\tsvc := New(k8sClient, hclog.NewNullLogger())\n\n\t\t\tgotPod, err := svc.GetPodInDaemonSet(context.Background(), tc.dsName, tc.namespace)\n\n\t\t\tif !tc.wantErr { // if we do not want an error then\n\t\t\t\tr.NoError(err)\n\t\t\t\tif tc.createPod {\n\t\t\t\t\tr.Equal(gotPod.Status.PodIP, tc.ipAddress)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tc.wantErr {\n\t\t\t\tr.Error(err)\n\t\t\t\tif tc.errMsg != \"\" {\n\t\t\t\t\tr.Contains(err.Error(), tc.errMsg)\n\t\t\t\t}\n\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/kubeops/deployment.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\n// GetPodInDeployment - Returns a running Pod in a deployment\nfunc (svc *Service) GetPodInDeployment(ctx context.Context, name, namespace string) (*corev1.Pod, error) {\n\t// check if the deployment actually exists\n\tdeploy, err := svc.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// get the list of replicaset in the current namespace\n\treplicaSets, err := svc.Client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar rsList []*appsv1.ReplicaSet\n\n\tvar rs *appsv1.ReplicaSet\n\n\t// Loop through the ReplicaSets and check if they are owned by the deployment\n\tfor index := range replicaSets.Items {\n\t\tif metav1.IsControlledBy(&replicaSets.Items[index], deploy) {\n\t\t\tr := replicaSets.Items[index]\n\t\t\tif *r.Spec.Replicas == 0 {\n\t\t\t\tsvc.Log.Info(\"replicaSet size set to zero\",\n\t\t\t\t\t\"name\", r.Name,\n\t\t\t\t\t\"deployment\", name,\n\t\t\t\t\t\"namespace\", namespace)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trsList = append(rsList, &r)\n\t\t}\n\t}\n\n\tif len(rsList) == 0 {\n\t\treturn nil, fmt.Errorf(\"could not find the replicaSet with replica count >=1 owned by the \"+\n\t\t\t\"deployment %s in namespace %s\", name, namespace)\n\t}\n\n\t// we use the first replicaset that has zero size\n\trs = rsList[0]\n\n\tif rs == nil || rs.Spec.Replicas == nil {\n\t\treturn nil, fmt.Errorf(\"could not find the replicaSet owned by the deployment %s in namespace %s\",\n\t\t\tname, namespace)\n\t}\n\n\tif *rs.Spec.Replicas < 1 {\n\t\tsvc.Log.Info(\"replicaSet size set to zero\",\n\t\t\t\"deployment\", name,\n\t\t\t\"namespace\", namespace)\n\n\t\treturn nil, fmt.Errorf(\"deployment %s in namespace %s has repliaset size set to zero\",\n\t\t\tname, namespace)\n\t}\n\n\t// we only want the Pods that are in running state\n\tfieldSelector := fields.OneTermEqualSelector(\n\t\t\"status.phase\",\n\t\t\"Running\",\n\t)\n\n\t// grab the labels associated with the ReplicaSet object\n\t// we only want to select the Pods, whose labels match that of the parent replicaSet\n\t// selector, err := labels.ValidatedSelectorFromSet(rs.Labels)\n\n\tselector, err := labels.ValidatedSelectorFromSet(rs.Spec.Selector.MatchLabels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// we now get a list of pods and find the ones owned by the replicaset\n\tpods, err := svc.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: selector.String(),\n\t\tFieldSelector: fieldSelector.String(),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpod, err := getRandomPodFromPodList(rs, pods)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to find any Pod associated with deployment %s in namespace %s: %w\",\n\t\t\tname, namespace, err)\n\t}\n\n\tsvc.Log.Info(\"Found Pod\", \"Parent-deployment\", name, \"Pod\", pod.Name,\n\t\t\"Namespace\", pod.Namespace, \"IP\", pod.Status.PodIP)\n\n\treturn pod, nil\n}\n"
  },
  {
    "path": "internal/kubeops/deployment_test.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/uuid\"\n\t\"k8s.io/utils/pointer\"\n\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc getDeploymentObject(name, namespace string, replicaSize int32) *appsv1.Deployment {\n\tdeploymentName := name\n\timageName := \"nginx\"\n\timageTag := \"latest\"\n\treplicas := pointer.Int32(replicaSize)\n\n\t// Create the Deployment object\n\tdeployment := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      deploymentName,\n\t\t\tNamespace: namespace,\n\t\t\tUID:       uuid.NewUUID(),\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: replicas,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\"app\": deploymentName,\n\t\t\t\t},\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"app\": deploymentName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  imageName,\n\t\t\t\t\t\t\tImage: fmt.Sprintf(\"%s:%s\", imageName, imageTag),\n\t\t\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:          \"http\",\n\t\t\t\t\t\t\t\t\tContainerPort: 80,\n\t\t\t\t\t\t\t\t\tProtocol:      corev1.ProtocolTCP,\n\t\t\t\t\t\t\t\t},\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\n\treturn deployment\n}\n\n// replicaSetWithOwnerSetToDeployment - creates a replicaset spec with owner reference to deploy\nfunc replicaSetWithOwnerSetToDeployment(deploy *appsv1.Deployment, size int32) *appsv1.ReplicaSet {\n\treplicaSet := &appsv1.ReplicaSet{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"replicaset-\" + deploy.Name,\n\t\t\tNamespace: \"default\",\n\t\t\tLabels:    deploy.Spec.Selector.MatchLabels,\n\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t*metav1.NewControllerRef(deploy, appsv1.SchemeGroupVersion.WithKind(\"Deployment\")),\n\t\t\t},\n\t\t},\n\t\tSpec: appsv1.ReplicaSetSpec{\n\t\t\tSelector: deploy.Spec.Selector,\n\t\t\tTemplate: deploy.Spec.Template,\n\t\t\tReplicas: pointer.Int32(size),\n\t\t},\n\t}\n\n\treplicaSet.UID = deploy.UID\n\n\treturn replicaSet\n}\n\n// podWithOwnerSetToReplicaSet - creates a Pod spec. and sets the ownder reference to rs\nfunc podWithOwnerSetToReplicaSet(rs *appsv1.ReplicaSet) *corev1.Pod {\n\tpod := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      rs.Name + \"-pod-foo\",\n\t\t\tNamespace: rs.Namespace,\n\t\t\tLabels:    rs.Labels,\n\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t*metav1.NewControllerRef(rs, appsv1.SchemeGroupVersion.WithKind(\"ReplicaSet\")),\n\t\t\t},\n\t\t},\n\t\tSpec: rs.Spec.Template.Spec,\n\t\tStatus: corev1.PodStatus{\n\t\t\tPhase: corev1.PodRunning,\n\t\t\tPodIP: \"192.168.0.1\",\n\t\t},\n\t}\n\n\treturn pod\n}\n\nfunc TestGetPodInDeployment(t *testing.T) {\n\tr := require.New(t)\n\t// build the new service object\n\tsvc := New(fake.NewSimpleClientset(), hclog.NewNullLogger())\n\n\tname := \"deploy1\"\n\tnamespace := \"default\"\n\tdeploySpec := getDeploymentObject(name, namespace, 1)\n\tctx := context.Background()\n\tdeployObj, err := svc.Client.AppsV1().Deployments(namespace).Create(ctx, deploySpec, metav1.CreateOptions{})\n\tr.NoError(err)\n\n\t// deployment will not create a replicaSet so when we call GetPodInDeployment, it should\n\t// give us an error specific to that\n\t_, err = svc.GetPodInDeployment(ctx, name, namespace)\n\tr.Error(err)\n\trsNotFoundMessage := fmt.Sprintf(\"could not find the replicaSet with replica count >=1 owned \"+\n\t\t\"by the deployment %s in namespace %s\",\n\t\tname, namespace)\n\tr.Equal(rsNotFoundMessage, err.Error())\n\n\t// now we create the replicaSet that is owned by the deployment that has zero replicas\n\trsSpec := replicaSetWithOwnerSetToDeployment(deployObj, 0)\n\t_, err = svc.Client.AppsV1().ReplicaSets(namespace).Create(ctx, rsSpec, metav1.CreateOptions{})\n\tr.NoError(err, \"failed to create replicaSet with size set to zero\")\n\t_, err = svc.GetPodInDeployment(ctx, name, namespace)\n\tr.Error(err)\n\t// rsSizeSetToZeroMsg := fmt.Sprintf(\"deployment %s in namespace %s has repliaset size set to zero\",\n\t//\tname, namespace)\n\tr.Equal(rsNotFoundMessage, err.Error())\n\n\t// we now modify the existing replicaSet and set the replica size to 1\n\trsSpec.Spec.Replicas = pointer.Int32(1)\n\trsObj, err := svc.Client.AppsV1().ReplicaSets(namespace).Update(ctx, rsSpec, metav1.UpdateOptions{})\n\tr.NoError(err, \"failed to update replicaset size set 1\")\n\t_, err = svc.GetPodInDeployment(ctx, name, namespace)\n\tpodNotFound := fmt.Sprintf(\"unable to find any Pod associated with deployment %s in namespace %s\",\n\t\tname, namespace)\n\tr.Contains(err.Error(), podNotFound)\n\n\t// we now create Pod that has no IP address set to it\n\t// but is in running stage and is owned by the replicaSet\n\t// this should also trigger an error\n\tpodSpec := podWithOwnerSetToReplicaSet(rsObj)\n\tpodSpec.Status.PodIP = \"\"\n\t_, err = svc.Client.CoreV1().Pods(namespace).Create(ctx, podSpec, metav1.CreateOptions{})\n\tr.NoError(err)\n\t_, err = svc.GetPodInDeployment(ctx, name, namespace)\n\tr.Contains(err.Error(), podNotFound)\n\n\t// finally we update the Pod to simulate an IP address allocation by CNI\n\tpodSpec.Status.PodIP = \"192.168.0.1\"\n\tcreatedPodObj, err := svc.Client.CoreV1().Pods(namespace).Update(ctx, podSpec, metav1.UpdateOptions{})\n\tr.NoError(err)\n\tgotPodObj, err := svc.GetPodInDeployment(ctx, name, namespace)\n\tr.NoError(err)\n\tr.Equal(createdPodObj, gotPodObj)\n}\n"
  },
  {
    "path": "internal/kubeops/pod.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n\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\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nconst (\n\tephContainerGroup   = \"\"                         // api group which provides the ephemeral container resource\n\tephContainerVersion = \"v1\"                       // api version which provides the ephemeral container resource\n\tephContainerKind    = \"Pod\"                      // core API Kind that provides the ephemeral container resource\n\tephContainerRes     = \"pods/ephemeralcontainers\" // name of the ephemeral container subresource\n)\n\n// GetPod - Returns a Running Pod that has an IP address allocated to it\nfunc (svc *Service) GetPod(ctx context.Context, name, namespace string) (*corev1.Pod, error) {\n\t// Get the Pod that matches the name and namespace\n\tpod, err := svc.Client.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})\n\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\tsvc.Log.Error(\"unable to find Pod\", \"namespace\", namespace, \"error\", err)\n\t\treturn nil, fmt.Errorf(\"unable to find Pod %s in namespace %s: %w\", name, namespace, err)\n\t}\n\n\tif err != nil {\n\t\tsvc.Log.Error(\"Unable to find Pod\", \"namespace\", namespace, \"error\", err)\n\t\treturn nil, err\n\t}\n\n\tif pod.Status.Phase != corev1.PodRunning {\n\t\treturn nil, fmt.Errorf(\"pod %s in namespace %s is not in running state: %s\",\n\t\t\tname,\n\t\t\tnamespace,\n\t\t\tpod.Status.Phase,\n\t\t)\n\t}\n\n\tif pod.Status.PodIP == \"\" {\n\t\treturn nil, fmt.Errorf(\"pod %s in namespace %s does not have an IP address\",\n\t\t\tname,\n\t\t\tnamespace,\n\t\t)\n\t}\n\n\treturn pod, nil\n}\n\n// getPodFromPodList - returns a random pod from PodList object\nfunc getRandomPodFromPodList(ownerObj metav1.Object, podList *corev1.PodList) (*corev1.Pod, error) {\n\tif ownerObj == nil {\n\t\treturn &corev1.Pod{}, fmt.Errorf(\"parameter ownerObj cannot be nil\")\n\t}\n\n\tif podList == nil {\n\t\treturn &corev1.Pod{}, fmt.Errorf(\"parameter podList cannot be nil\")\n\t}\n\n\tvar pods []*corev1.Pod\n\n\tfor _, pod := range podList.Items {\n\n\t\ttmpPod := pod\n\t\tif !metav1.IsControlledBy(&tmpPod, ownerObj) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif tmpPod.Status.Phase != corev1.PodRunning {\n\t\t\tcontinue\n\t\t}\n\n\t\tif tmpPod.Status.PodIP == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t// this pod is owned by the parent object\n\t\t// the pod is in running state\n\t\t// the pod also has IP address allocated by CNI\n\t\tpods = append(pods, &tmpPod)\n\t}\n\n\tif len(pods) == 0 {\n\t\treturn &corev1.Pod{}, fmt.Errorf(\"unable to find a Pod\")\n\t}\n\n\tindex := rand.Intn(len(pods))\n\treturn pods[index], nil\n}\n\n// CheckEphemeralContainerSupport - Checks support for ephemeral containers\nfunc (svc *Service) CheckEphemeralContainerSupport(ctx context.Context) error {\n\t// check if the kubernetes server has support for ephemeral containers\n\n\tfound, err := checkResourceSupport(ctx, svc.Client, ephContainerGroup, ephContainerVersion,\n\t\tephContainerKind, ephContainerRes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// we have not found the resource\n\tif !found {\n\t\treturn fmt.Errorf(\"unable to find K8s resource=%q, Group=%q, Version=%q Kind=%q\",\n\t\t\tephContainerRes, ephContainerGroup, ephContainerVersion, ephContainerKind)\n\t}\n\n\treturn nil\n}\n\n// checkResourceSupport - Checks support for a specific resource\nfunc checkResourceSupport(\n\tctx context.Context,\n\tk8sClient kubernetes.Interface,\n\tgroup, version, kind, resourceName string,\n) (bool, error) {\n\tgroupVersion := schema.GroupVersion{Group: group, Version: version}\n\n\tdClient := k8sClient.Discovery()\n\n\tresourceList, err := dClient.ServerResourcesForGroupVersion(groupVersion.String())\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to get API resource list for %q: %w\", groupVersion.String(), err)\n\t}\n\n\tfor _, resource := range resourceList.APIResources {\n\t\tif resource.Kind == kind && resource.Name == resourceName {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\n// PingHealthEndpoint - pings a single endpoint of the apiServer using HTTP\nfunc (svc *Service) PingHealthEndpoint(ctx context.Context, endpoint string) error {\n\tpingRequest := svc.Client.CoreV1().RESTClient().Get().AbsPath(endpoint)\n\n\tif err := pingRequest.Do(ctx).Error(); err != nil {\n\t\treturn fmt.Errorf(\"unable to HTTP ping \"+endpoint+\" of the API server: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (svc *Service) WaitForPodInResourceReady(name, namespace, resourceType string,\n\tpoll, timeout time.Duration,\n) error {\n\tvar fn func(context.Context, string, string) (*corev1.Pod, error)\n\n\tswitch strings.ToLower(resourceType) {\n\tcase \"deployment\":\n\t\tfn = svc.GetPodInDeployment\n\tcase \"daemonset\":\n\t\tfn = svc.GetPodInDaemonSet\n\tcase \"statefulset\":\n\t\tfn = svc.GetPodInStatefulSet\n\tcase \"pod\":\n\t\tfn = svc.GetPod\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported resource type %q\", resourceType)\n\t}\n\n\ttimeOutCh := time.After(timeout)\n\tticker := time.NewTicker(poll)\n\n\ttrigger := make(chan struct{}, 1)\n\ttrigger <- struct{}{}\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeOutCh:\n\t\t\treturn fmt.Errorf(\"timed out getting pod for %q - %s/%s, timeout duration=%v\", resourceType,\n\t\t\t\tnamespace, name, timeout.String())\n\t\tcase <-trigger:\n\t\tcase <-ticker.C:\n\t\t}\n\n\t\tlog.Println(\"polling for object\", name, namespace, resourceType)\n\t\tsvc.Log.Info(\"polling\", \"name\", name, \"namespace\", namespace)\n\t\t_, err := fn(context.Background(), name, namespace)\n\t\tif err == nil {\n\t\t\tlog.Printf(\"Found name=%s namespace=%s  resourceType=%s\", name, namespace, resourceType)\n\t\t\treturn nil\n\t\t}\n\t\tlog.Println(\"error while polling for object, retrying...\", name, namespace, resourceType, err)\n\t}\n}\n"
  },
  {
    "path": "internal/kubeops/pod_test.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestGetPod(t *testing.T) {\n\t// create a fake clientset that will store state in memory\n\tfakeClient := fake.NewSimpleClientset()\n\n\tctx := context.Background()\n\t// initialise our Service\n\tsvc := Service{\n\t\tClient: fakeClient,\n\t\tLog:    hclog.NewNullLogger(),\n\t}\n\n\t// create a testNamespace and testPod\n\ttestNamespace := \"foo-ns\"\n\ttestPodName := \"bar-pod\"\n\ttestPodIP := \"192.168.168.100\"\n\ttestPod := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      testPodName,\n\t\t\tNamespace: testNamespace,\n\t\t},\n\t\tStatus: corev1.PodStatus{\n\t\t\tPhase: corev1.PodRunning,\n\t\t\tPodIP: testPodIP,\n\t\t},\n\t}\n\n\tr := require.New(t)\n\n\t// create and then add the test pod to the fake clientset\n\t_, err := fakeClient.CoreV1().Pods(testNamespace).Create(context.Background(), testPod, metav1.CreateOptions{})\n\n\tr.NoError(err, \"unable to add test Pod to the fake client\")\n\n\ttestCases := []struct {\n\t\tname      string\n\t\tnamespace string\n\t\tpodName   string\n\t\tpodIP     string\n\t\tphase     corev1.PodPhase\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\tname:      \"GetPod with valid inputs should return the correct pod\",\n\t\t\tnamespace: testNamespace,\n\t\t\tpodName:   testPodName,\n\t\t\tpodIP:     testPodIP,\n\t\t\tphase:     corev1.PodRunning,\n\t\t\twantErr:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"GetPod should return an error when the pod is not in running state\",\n\t\t\tnamespace: testNamespace,\n\t\t\tpodName:   testPodName,\n\t\t\tpodIP:     testPodIP,\n\t\t\tphase:     corev1.PodPending,\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"GetPod should return an error when the pod does not have an IP address\",\n\t\t\tnamespace: testNamespace,\n\t\t\tpodName:   testPodName,\n\t\t\tpodIP:     \"\",\n\t\t\tphase:     corev1.PodRunning,\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"GetPod should return an error when the pod is not found\",\n\t\t\tnamespace: testNamespace,\n\t\t\tpodName:   \"this-pod-does-not-exist\",\n\t\t\tpodIP:     \"\",\n\t\t\tphase:     corev1.PodPending,\n\t\t\twantErr:   true,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\t// update the test pod with the desired phase and podIP\n\t\t\ttestPod.Status.Phase = testCase.phase\n\t\t\ttestPod.Status.PodIP = testCase.podIP\n\n\t\t\t// update the fake clientset with the updated pod that has some fields missing or empty\n\t\t\t_, err := fakeClient.CoreV1().Pods(testNamespace).Update(ctx, testPod, metav1.UpdateOptions{})\n\n\t\t\tr.NoError(err, \"unable to update the Pod in the fake clientset\")\n\t\t\tresult, err := svc.GetPod(ctx, testCase.podName, testCase.namespace)\n\n\t\t\tif testCase.wantErr {\n\t\t\t\tr.Error(err, \"wanted an error, but got nil\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tr.NoError(err, \"wanted no error, but got %v\", err)\n\t\t\tr.NotNil(result, \"wanted a pod, but got nil\")\n\t\t\tr.Equal(testCase.podName, result.Name, \"wanted pod name to be %s, but got %s\", testCase.podName, result.Name)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/kubeops/statefulset.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\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\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\n// GetPodInStatefulSet - Returns a running Pod in a statefulset\nfunc (svc *Service) GetPodInStatefulSet(ctx context.Context, name, namespace string) (*corev1.Pod, error) {\n\t// check if the statefulset actually exists\n\tss, err := svc.Client.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\treturn nil, fmt.Errorf(\"unable to find statefulset %s in namespace %s: %w\", name, namespace, err)\n\t\t}\n\t\treturn nil, err\n\t}\n\n\t// ensure that the statefulset has replia count set to at least 1\n\n\tif *ss.Spec.Replicas == 0 {\n\t\tsvc.Log.Error(\"Stateful set has zero replicas\", \"Statefulset\",\n\t\t\tname, \"Namespace\", namespace)\n\t\treturn nil, fmt.Errorf(\"zero replicas were found in the statefulset %s in namespace %s\",\n\t\t\tname, namespace)\n\t}\n\n\tfieldSelector := fields.OneTermEqualSelector(\n\t\t\"status.phase\",\n\t\t\"Running\",\n\t).String()\n\n\t// Grab the labels from the embedded Pod spec templates\n\t// podLabels := labels.FormatLabels(ss.Spec.Template.Labels)\n\tpodLabels := labels.FormatLabels(ss.Spec.Selector.MatchLabels)\n\n\tpods, err := svc.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: podLabels,\n\t\tFieldSelector: fieldSelector,\n\t})\n\tif err != nil {\n\t\tsvc.Log.Error(\"Unable list Pods\", \"Namespace\", namespace, \"error\", err)\n\t\treturn nil, fmt.Errorf(\"unable to list Pods in namespace: %w\", err)\n\t}\n\n\tpod, err := getRandomPodFromPodList(ss, pods)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to find Pod owned by Statefulset %s in namespace %s: %w\",\n\t\t\tname, namespace, err)\n\t}\n\n\tsvc.Log.Info(\"Found running Pod owned by StatefulSet\", \"StatefulSetName\", name,\n\t\t\"Namespace\", namespace, \"Pod\", pod.Name, \"PodIP\", pod.Status.PodIP)\n\n\treturn pod, nil\n}\n"
  },
  {
    "path": "internal/kubeops/statefulset_test.go",
    "content": "package kubeops\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/hashicorp/go-hclog\"\n\t\"github.com/stretchr/testify/require\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\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\t\"k8s.io/utils/pointer\"\n)\n\n// createStatefulSet - creates a statefulset and returns the same\nfunc createStatefulSet(client kubernetes.Interface, name, namespace string, replicas int32, t *testing.T) *appsv1.StatefulSet {\n\tlabels := map[string]string{\n\t\t\"app\": \"nginx\",\n\t}\n\tselector := &metav1.LabelSelector{\n\t\tMatchLabels: labels,\n\t}\n\n\tpodTemplate := corev1.PodTemplateSpec{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tLabels: labels,\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{\n\t\t\t\t{\n\t\t\t\t\tName:  \"nginx\",\n\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:          \"http\",\n\t\t\t\t\t\t\tContainerPort: 80,\n\t\t\t\t\t\t\tProtocol:      corev1.ProtocolTCP,\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\n\tstatefulSet := &appsv1.StatefulSet{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\tReplicas:    pointer.Int32(replicas),\n\t\t\tServiceName: \"nginx-service\",\n\t\t\tSelector:    selector,\n\t\t\tTemplate:    podTemplate,\n\t\t\tVolumeClaimTemplates: []corev1.PersistentVolumeClaim{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"data\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: corev1.PersistentVolumeClaimSpec{\n\t\t\t\t\t\tAccessModes: []corev1.PersistentVolumeAccessMode{\n\t\t\t\t\t\t\tcorev1.ReadWriteOnce,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tResources: corev1.VolumeResourceRequirements{\n\t\t\t\t\t\t\tRequests: corev1.ResourceList{\n\t\t\t\t\t\t\t\tcorev1.ResourceStorage: resource.MustParse(\"1Gi\"),\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\n\tobj, err := client.AppsV1().StatefulSets(namespace).Create(context.Background(), statefulSet, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create stateful set %s: %v\", name, err)\n\t}\n\n\treturn obj\n}\n\n// createStatefulSetPod - creates a new pod in the statefulset with index set to index\nfunc createStatefulSetPod(\n\tclient kubernetes.Interface,\n\tstatefulSet *appsv1.StatefulSet,\n\tipAddress string,\n\tindex int,\n\tt *testing.T,\n) *corev1.Pod {\n\tpod := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      fmt.Sprintf(\"%s-%d\", statefulSet.Name, index),\n\t\t\tNamespace: statefulSet.Namespace,\n\t\t\tLabels:    statefulSet.Spec.Template.ObjectMeta.Labels,\n\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t*metav1.NewControllerRef(statefulSet, appsv1.SchemeGroupVersion.WithKind(\"StatefulSet\")),\n\t\t\t},\n\t\t},\n\t\tSpec: statefulSet.Spec.Template.Spec,\n\t\tStatus: corev1.PodStatus{\n\t\t\tPhase: corev1.PodRunning,\n\t\t\tPodIP: ipAddress,\n\t\t},\n\t}\n\n\tpodObj, err := client.CoreV1().Pods(pod.Namespace).Create(context.Background(), pod, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create pod owned by statefulset %v: %v\", statefulSet.Name, err)\n\t}\n\n\treturn podObj\n}\n\nfunc TestGetPodInStatefulSet(t *testing.T) {\n\ttestCases := []struct {\n\t\tname              string\n\t\tipAddress         string\n\t\tssName            string\n\t\tssNamespace       string\n\t\treplicas          int32\n\t\tcreatePod         bool\n\t\tcreateStatefulset bool\n\t\twantErr           bool\n\t\terrMsg            string\n\t}{\n\t\t{\n\t\t\tname:              \"when both StatefulSet and Pod does not exist\",\n\t\t\tipAddress:         \"192.168.0.1\",\n\t\t\tssName:            \"web\",\n\t\t\tssNamespace:       \"default\",\n\t\t\tcreatePod:         false,\n\t\t\treplicas:          0,\n\t\t\tcreateStatefulset: false,\n\t\t\twantErr:           true,\n\t\t\terrMsg:            `unable to find statefulset`,\n\t\t},\n\t\t{\n\t\t\tname:              \"when StatefulSet exists but the Pod does not exist\",\n\t\t\tipAddress:         \"192.168.0.1\",\n\t\t\tssName:            \"web\",\n\t\t\tssNamespace:       \"default\",\n\t\t\tcreatePod:         false,\n\t\t\treplicas:          0,\n\t\t\tcreateStatefulset: true,\n\t\t\twantErr:           true,\n\t\t\terrMsg:            `zero replicas were found in the statefulset`,\n\t\t},\n\t\t{\n\t\t\tname:              \"when both StatefulSet and Pod exist\",\n\t\t\tipAddress:         \"192.168.0.1\",\n\t\t\tssName:            \"web\",\n\t\t\tssNamespace:       \"default\",\n\t\t\tcreatePod:         true,\n\t\t\treplicas:          1,\n\t\t\tcreateStatefulset: true,\n\t\t\twantErr:           false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\ttc := testCase\n\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tr := require.New(t)\n\t\t\tclient := fake.NewSimpleClientset()\n\n\t\t\tsvc := New(client, hclog.NewNullLogger())\n\n\t\t\tvar (\n\t\t\t\tss  *appsv1.StatefulSet\n\t\t\t\tpod *corev1.Pod\n\t\t\t)\n\n\t\t\tif tc.createStatefulset {\n\t\t\t\tss = createStatefulSet(client, tc.ssName, tc.ssNamespace, tc.replicas, t)\n\t\t\t}\n\n\t\t\tif tc.createPod {\n\t\t\t\tpod = createStatefulSetPod(client, ss, tc.ipAddress, 0, t)\n\t\t\t}\n\n\t\t\tgotPod, err := svc.GetPodInStatefulSet(context.Background(), tc.ssName, tc.ssNamespace)\n\n\t\t\t// if we do not expect and error in the test case\n\t\t\tif !tc.wantErr {\n\t\t\t\tr.NoError(err)\n\t\t\t\t// if we have created both pods and statefulset\n\t\t\t\tif tc.createPod && tc.createStatefulset {\n\t\t\t\t\tr.Equal(gotPod, pod)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// if tc.wantErr {\n\t\t\t// we are expecting an error\n\t\t\tr.Error(err)\n\t\t\t// check error message with the one defined in the test case\n\t\t\tif tc.errMsg != \"\" {\n\t\t\t\tr.Contains(err.Error(), tc.errMsg)\n\t\t\t}\n\t\t\t//}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/kubeops/string_gen.go",
    "content": "package kubeops\n\nimport (\n\t\"math/rand\"\n\n\t\"github.com/google/uuid\"\n)\n\nconst (\n\tcharset = \"abcdefghijklmnopqrstuvwxyz123456789\"\n)\n\n// NewUUIDString - generates a new UUID and converts it to string\nfunc NewUUIDString() (string, error) {\n\tid, err := uuid.NewUUID()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn id.String(), nil\n}\n\n// RandString - generates random string with length\nfunc RandString(length int) string {\n\t// One change made in Go 1.20 is that math/rand is now random by default.\n\tb := make([]byte, length)\n\tfor i := range b {\n\t\tb[i] = charset[rand.Intn(len(charset))]\n\t}\n\treturn string(b)\n}\n"
  },
  {
    "path": "internal/logger/hclog.go",
    "content": "package logger\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/hashicorp/go-hclog\"\n)\n\n// NewHCLogger - return an instance of the logger\nfunc NewHCLogger(logLevel, appName string, w io.Writer) hclog.Logger {\n\thcLevel := hclog.LevelFromString(strings.ToUpper(logLevel))\n\n\tif hcLevel == hclog.NoLevel {\n\t\thcLevel = hclog.Info\n\t}\n\n\tvar includeLocation bool\n\n\tif hcLevel <= hclog.Debug {\n\t\t// check if hcLevel is DEBUG or more verbose\n\t\tincludeLocation = true\n\t}\n\n\tl := hclog.New(&hclog.LoggerOptions{\n\t\tName:            fmt.Sprintf(\"[%s]\", appName),\n\t\tLevel:           hcLevel,\n\t\tOutput:          w,\n\t\tJSONFormat:      false,\n\t\tIncludeLocation: includeLocation,\n\t})\n\n\treturn l\n}\n"
  },
  {
    "path": "justfile",
    "content": "default:\n    just --list\n\nversion := \"0.0.1\"\n\n# build the binary in ./bin folder\nbuild:\n\tgo build -o bin/netassert cmd/netassert/cli/*.go\n\n# build and run the binary\nrun: build\n\tbin/netassert\n\n# run go test(s)\ntest:\n\tgo test -v -race ./...\n\n# run the linter\nlint:\n\tgolangci-lint run ./...\n\n# remove the binary from ./bin folder\nclean:\n\t@rm -rf ./bin\n\n# create a new kind k8s cluster called packet-test\nkind-up:\n\tkind create cluster --name packet-test --config ./e2e/clusters/kind/kind-config.yaml\n\n# delete the kind k8s cluster called packet-test\nkind-down:\n\tkind delete clusters packet-test\n# deployObj kubernetes manifests\nk8s-apply:\n\tkubectl apply -f ./e2e/manifests/workload.yaml\n\nk8s-rm-apply:\n\tkubectl delete -f ./e2e/manifests/workload.yaml\n\nnetpol-apply:\n\tkubectl apply -f ./e2e/manifests/networkpolicies.yaml\n\nnetpol-rm-apply:\n\tkubectl delete -f ./e2e/manifests/networkpolicies.yaml\n\ncalico-apply:\n\tkubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.3/manifests/calico.yaml\n\ncalico-rm-apply:\n\tkubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.3/manifests/calico.yaml\n\n# build docker image and tag it 0.0.01\ndocker-build:\n\tdocker build -f Dockerfile --no-cache --tag packet-capture:{{version}} .\n\n# import image into the local kind cluster called packet-test\nkind-import-image:\n    kind load docker-image packet-capture:{{version}} --name packet-test && kind load docker-image netassert-client:{{version}} --name packet-test\n\n"
  },
  {
    "path": "rbac/cluster-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: netassert\nrules:\n- apiGroups:\n  - \"\"\n  - \"apps\"\n  resources:\n  - deployments\n  - statefulsets\n  - daemonsets\n  - pods\n  verbs:\n  - get\n##\n- apiGroups:\n  - \"\"\n  - \"apps\"\n  resources:\n  - replicasets\n  - pods\n  verbs:\n  - list\n##\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  - pods/ephemeralcontainers\n  verbs:\n  - watch\n  - patch"
  },
  {
    "path": "rbac/cluster-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: netassert\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: netassert\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: User\n  name: netassert-user"
  }
]