[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Introduction\n\nDiversity and inclusion make our community strong. We encourage participation from the most varied and diverse backgrounds possible and want to be very clear about where we stand.\n\nOur goal is to maintain a safe, helpful and friendly community for everyone, regardless of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other defining characteristic.\n\nThis code and related procedures also apply to unacceptable behavior occurring outside the scope of community activities, in all community venues (online and in-person) as well as in all one-on-one communications, and anywhere such behavior has the potential to adversely affect the safety and well-being of community members.\n\nFor more information on our code of conduct, please visit [https://slackhq.github.io/code-of-conduct](https://slackhq.github.io/code-of-conduct)\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributors Guide\n\nInterested in contributing? Awesome! Before you do though, please read our\n[Code of Conduct](https://slackhq.github.io/code-of-conduct). We take it very seriously, and expect that you will as\nwell.\n\nThere are many ways you can contribute! :heart:\n\n### Bug Reports and Fixes :bug:\n-  If you find a bug, please search for it in the [Issues](https://github.com/slackhq/simple-kubernetes-webhook/issues), and if it isn't already tracked,\n   [create a new issue](https://github.com/slackhq/simple-kubernetes-webhook/issues/new). Fill out the \"Bug Report\" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still\n   be reviewed.\n-  Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`.\n-  If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number.\n  -  Include tests that isolate the bug and verifies that it was fixed.\n\n### New Features :bulb:\n-  If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackhq/simple-kubernetes-webhook/issues/new).\n-  Issues that have been identified as a feature request will be labelled `enhancement`.\n-  If you'd like to implement the new feature, please wait for feedback from the project\n   maintainers before spending too much time writing the code. In some cases, `enhancement`s may\n   not align well with the project objectives at the time.\n\n### Tests :mag:, Documentation :books:, Miscellaneous :sparkles:\n-  If you'd like to improve the tests, you want to make the documentation clearer, you have an\n   alternative implementation of something that may have advantages over the way its currently\n   done, or you have any other change, we would be happy to hear about it!\n  -  If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind.\n  -  If not, [open an Issue](https://github.com/slackhq/simple-kubernetes-webhook/issues/new) to discuss the idea first.\n\nIf you're new to our project and looking for some way to make your first contribution, look for\nIssues labelled `good first contribution`.\n\n## Requirements\n\nFor your contribution to be accepted:\n\n- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackhq/simple-kubernetes-webhook).\n- [x] The test suite must be complete and pass.\n- [x] The changes must be approved by code review.\n- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.\n\nIf the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created.\n\n[Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z)\n\n## Creating a Pull Request\n\n1.  :fork_and_knife: Fork the repository on GitHub.\n2.  :runner: Clone/fetch your fork to your local development machine. It's a good idea to run the tests just\n    to make sure everything is in order.\n3.  :herb: Create a new branch and check it out.\n4.  :crystal_ball: Make your changes and commit them locally. Magic happens here!\n5.  :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-16`).\n6.  :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `master` in this\n    repository.\n\n## Maintainers\n\nThere are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "### Description\n\nDescribe your issue here.\n\n### What type of issue is this? (place an `x` in one of the `[ ]`)\n- [ ] bug\n- [ ] enhancement (feature request)\n- [ ] question\n- [ ] documentation related\n- [ ] testing related\n- [ ] discussion\n\n### Requirements (place an `x` in each of the `[ ]`)\n* [ ] I've read and understood the [Contributing guidelines](https://github.com/slackhq/simple-kubernetes-webhook/blob/master/.github/contributing.md) and have done my best effort to follow them.\n* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).\n* [ ] I've searched for any related issues and avoided creating a duplicate issue.\n\n---\n\n### Bug Report\n\nFilling out the following details about bugs will help us solve your issue sooner.\n\n#### Reproducible in:\n\nsimple-kubernetes-webhook version:\n\nGo version:\n\nOS version(s):\n\n#### Steps to reproduce:\n\n1.\n2.\n3.\n\n#### Expected result:\n\nWhat you expected to happen\n\n#### Actual result:\n\nWhat actually happened\n\n#### Attachments:\n\nLogs, screenshots, screencast, sample project, funny gif, etc.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "###  Summary\n\nDescribe the goal of this PR. Mention any related Issue numbers.\n\n### Requirements (place an `x` in each `[ ]`)\n\n* [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackhq/simple-kubernetes-webhook/blob/master/.github/contributing.md) and have done my best effort to follow them.\n* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).\n\n> The following point can be removed after setting up CI (such as Travis) with coverage reports (such as Codecov)\n\n* [ ] I've written tests to cover the new code and functionality included in this PR.\n\n> The following point can be removed after setting up a CLA reporting tool such as cla-assistant.io\n\n* [ ] I've read, agree to, and signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackhq/simple-kubernetes-webhook).\n"
  },
  {
    "path": ".github/maintainers_guide.md",
    "content": "# Maintainers Guide\n\nThis document describes tools, tasks and workflow that one needs to be familiar with in order to effectively maintain\nthis project. If you use this package within your own software as is but don't plan on modifying it, this guide is\n**not** for you.\n\n## Tools (optional)\n\nTools, dependencies, or other programs someone maintaining this project needs to be familiar with:\n* Kubernetes\n* Go\n* Docker\n* Kind\n\n## Tasks\n\n### Testing\n\nUnit can be run like so:\n```\n❯ go test ./...\n?   \tgithub.com/slackhq/simple-kubernetes-webhook\t[no test files]\nok  \tgithub.com/slackhq/simple-kubernetes-webhook/pkg/admission\t0.743s\nok  \tgithub.com/slackhq/simple-kubernetes-webhook/pkg/mutation\t1.065s\nok  \tgithub.com/slackhq/simple-kubernetes-webhook/pkg/validation\t0.413s\n```\n\n### TLS certificate\nKubernetes only allows admission webhooks running with `https`. To generate a TLS secret, run [`./dev/gen-certs.sh`](/dev/gen-certs.sh). The base64 caBundle needs to be manually copied and pasted in the `MutatingWebhookConfiguration` and `ValidattingWebhookConfiguration` at [`./dev/manifests/cluster-config/`](./dev/manifests/cluster-config/)\n\n### Logs\nThe logs level defaults to `debug` and can be set with the env var:\n```\nLOG_LEVEL=info\n```\nThe logs format defaults to `text` and can be set to `json` with the env var:\n```\nLOG_JSON=true\n```\n\n### Releasing\n\nN/A: this demo project is not released\n\n## Workflow\n\n### Versioning and Tags\n\nN/A: this demo project is not released\n\n### Branches\n\nThe `main` branch is where active development occurs, feel free to name your feature / bug fix branch what your heart desires.\n\n### Issue Management\n\nLabels are used to run issues through an organized workflow. Here are the basic definitions:\n\n*  `bug`: A confirmed bug report. A bug is considered confirmed when reproduction steps have been\n   documented and the issue has been reproduced.\n*  `enhancement`: A feature request for something this package might not already do.\n*  `docs`: An issue that is purely about documentation work.\n*  `tests`: An issue that is purely about testing work.\n*  `needs feedback`: An issue that may have claimed to be a bug but was not reproducible, or was otherwise missing some information.\n*  `discussion`: An issue that is purely meant to hold a discussion. Typically the maintainers are looking for feedback in this issues.\n*  `question`: An issue that is like a support request because the user's usage was not correct.\n*  `semver:major|minor|patch`: Metadata about how resolving this issue would affect the version number.\n*  `security`: An issue that has special consideration for security reasons.\n*  `good first contribution`: An issue that has a well-defined relatively-small scope, with clear expectations. It helps when the testing approach is also known.\n*  `duplicate`: An issue that is functionally the same as another issue. Apply this only if you've linked the other issue by number.\n\n> You may want to add more labels for subsystems of your project, depending on how complex it is.\n\n**Triage** is the process of taking new issues that aren't yet \"seen\" and marking them with a basic\nlevel of information with labels. An issue should have **one** of the following labels applied:\n`bug`, `enhancement`, `question`, `needs feedback`, `docs`, `tests`, or `discussion`.\n\nIssues are closed when a resolution has been reached. If for any reason a closed issue seems\nrelevant once again, reopening is great and better than creating a duplicate issue.\n\n## Everything else\n\nWhen in doubt, find the other maintainers and ask.\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "# Comment line immediately above ownership line is reserved for related other information. Please be careful while editing.\n#ECCN:Open Source\n#GUSINFO:Open Source,Open Source Workflow\n"
  },
  {
    "path": "Dockerfile",
    "content": "# syntax=docker/dockerfile:experimental\n# ---\nFROM golang:1.16 AS build\n\nENV GOOS=linux\nENV GOARCH=amd64\nENV CGO_ENABLED=0\n\nWORKDIR /work\nCOPY . /work\n\n# Build admission-webhook\nRUN --mount=type=cache,target=/root/.cache/go-build,sharing=private \\\n  go build -o bin/admission-webhook .\n\n# ---\nFROM scratch AS run\n\nCOPY --from=build /work/bin/admission-webhook /usr/local/bin/\n\nCMD [\"admission-webhook\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) Slack Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "Makefile",
    "content": ".PHONY: test\ntest:\n\t@echo \"\\n🛠️  Running unit tests...\"\n\tgo test ./...\n\n.PHONY: build\nbuild:\n\t@echo \"\\n🔧  Building Go binaries...\"\n\tGOOS=darwin GOARCH=amd64 go build -o bin/admission-webhook-darwin-amd64 .\n\tGOOS=linux GOARCH=amd64 go build -o bin/admission-webhook-linux-amd64 .\n\n.PHONY: docker-build\ndocker-build:\n\t@echo \"\\n📦 Building simple-kubernetes-webhook Docker image...\"\n\tdocker build -t simple-kubernetes-webhook:latest .\n\n# From this point `kind` is required\n.PHONY: cluster\ncluster:\n\t@echo \"\\n🔧 Creating Kubernetes cluster...\"\n\tkind create cluster --config dev/manifests/kind/kind.cluster.yaml\n\n.PHONY: delete-cluster\ndelete-cluster:\n\t@echo \"\\n♻️  Deleting Kubernetes cluster...\"\n\tkind delete cluster\n\n.PHONY: push\npush: docker-build\n\t@echo \"\\n📦 Pushing admission-webhook image into Kind's Docker daemon...\"\n\tkind load docker-image simple-kubernetes-webhook:latest\n\n.PHONY: deploy-config\ndeploy-config:\n\t@echo \"\\n⚙️  Applying cluster config...\"\n\tkubectl apply -f dev/manifests/cluster-config/\n\n.PHONY: delete-config\ndelete-config:\n\t@echo \"\\n♻️  Deleting Kubernetes cluster config...\"\n\tkubectl delete -f dev/manifests/cluster-config/\n\n.PHONY: deploy\ndeploy: push delete deploy-config\n\t@echo \"\\n🚀 Deploying simple-kubernetes-webhook...\"\n\tkubectl apply -f dev/manifests/webhook/\n\n.PHONY: delete\ndelete:\n\t@echo \"\\n♻️  Deleting simple-kubernetes-webhook deployment if existing...\"\n\tkubectl delete -f dev/manifests/webhook/ || true\n\n.PHONY: pod\npod:\n\t@echo \"\\n🚀 Deploying test pod...\"\n\tkubectl apply -f dev/manifests/pods/lifespan-seven.pod.yaml\n\n.PHONY: delete-pod\ndelete-pod:\n\t@echo \"\\n♻️ Deleting test pod...\"\n\tkubectl delete -f dev/manifests/pods/lifespan-seven.pod.yaml\n\n.PHONY: bad-pod\nbad-pod:\n\t@echo \"\\n🚀 Deploying \\\"bad\\\" pod...\"\n\tkubectl apply -f dev/manifests/pods/bad-name.pod.yaml\n\n.PHONY: delete-bad-pod\ndelete-bad-pod:\n\t@echo \"\\n🚀 Deleting \\\"bad\\\" pod...\"\n\tkubectl delete -f dev/manifests/pods/bad-name.pod.yaml\n\n.PHONY: taint\ntaint:\n\t@echo \"\\n🎨 Taining Kubernetes node..\"\n\tkubectl taint nodes kind-control-plane \"acme.com/lifespan-remaining\"=4:NoSchedule\n\n.PHONY: logs\nlogs:\n\t@echo \"\\n🔍 Streaming simple-kubernetes-webhook logs...\"\n\tkubectl logs -l app=simple-kubernetes-webhook -f\n\n.PHONY: delete-all\ndelete-all: delete delete-config delete-pod delete-bad-pod\n"
  },
  {
    "path": "README.md",
    "content": "# simple-kubernetes-webhook\n\nThis is a simple [Kubernetes admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/). It is meant to be used as a validating and mutating admission webhook only and does not support any controller logic. It has been developed as a simple Go web service without using any framework or boilerplate such as kubebuilder.\n\nThis project is aimed at illustrating how to build a fully functioning admission webhook in the simplest way possible. Most existing examples found on the web rely on heavy machinery using powerful frameworks, yet fail to illustrate how to implement a lightweight webhook that can do much needed actions such as rejecting a pod for compliance reasons, or inject helpful environment variables.\n\nFor readability, this project has been stripped of the usual production items such as: observability instrumentation, release scripts, redundant deployment configurations, etc. As such, it is not meant to use as-is in a production environment. This project is, in fact, a simplified fork of a system used accross all Kubernetes production environments at Slack.\n\n## Installation\nThis project can fully run locally and includes automation to deploy a local Kubernetes cluster (using Kind).\n\n### Requirements\n* Docker\n* kubectl\n* [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)\n* Go >=1.16 (optional)\n\n## Usage\n### Create Cluster\nFirst, we need to create a Kubernetes cluster:\n```\n❯ make cluster\n\n🔧 Creating Kubernetes cluster...\nkind create cluster --config dev/manifests/kind/kind.cluster.yaml\nCreating cluster \"kind\" ...\n ✓ Ensuring node image (kindest/node:v1.21.1) 🖼\n ✓ Preparing nodes 📦\n ✓ Writing configuration 📜\n ✓ Starting control-plane 🕹️\n ✓ Installing CNI 🔌\n ✓ Installing StorageClass 💾\nSet kubectl context to \"kind-kind\"\nYou can now use your cluster with:\n\nkubectl cluster-info --context kind-kind\n\nHave a nice day! 👋\n```\n\nMake sure that the Kubernetes node is ready:\n```\n❯ kubectl get nodes\nNAME                 STATUS   ROLES                  AGE     VERSION\nkind-control-plane   Ready    control-plane,master   3m25s   v1.21.1\n```\n\nAnd that system pods are running happily:\n```\n❯ kubectl -n kube-system get pods\nNAME                                         READY   STATUS    RESTARTS   AGE\ncoredns-558bd4d5db-thwvj                     1/1     Running   0          3m39s\ncoredns-558bd4d5db-w85ks                     1/1     Running   0          3m39s\netcd-kind-control-plane                      1/1     Running   0          3m56s\nkindnet-84slq                                1/1     Running   0          3m40s\nkube-apiserver-kind-control-plane            1/1     Running   0          3m54s\nkube-controller-manager-kind-control-plane   1/1     Running   0          3m56s\nkube-proxy-4h6sj                             1/1     Running   0          3m40s\nkube-scheduler-kind-control-plane            1/1     Running   0          3m54s\n```\n\n### Deploy Admission Webhook\nTo configure the cluster to use the admission webhook and to deploy said webhook, simply run:\n```\n❯ make deploy\n\n📦 Building simple-kubernetes-webhook Docker image...\ndocker build -t simple-kubernetes-webhook:latest .\n[+] Building 14.3s (13/13) FINISHED\n...\n\n📦 Pushing admission-webhook image into Kind's Docker daemon...\nkind load docker-image simple-kubernetes-webhook:latest\nImage: \"simple-kubernetes-webhook:latest\" with ID \"sha256:46b8603bcc11a8fa1825190d3ed99c099096395b22a709e13ec6e7ae2f54014d\" not yet present on node \"kind-control-plane\", loading...\n\n⚙️  Applying cluster config...\nkubectl apply -f dev/manifests/cluster-config/\nnamespace/apps created\nmutatingwebhookconfiguration.admissionregistration.k8s.io/simple-kubernetes-webhook.acme.com created\nvalidatingwebhookconfiguration.admissionregistration.k8s.io/simple-kubernetes-webhook.acme.com created\n\n🚀 Deploying simple-kubernetes-webhook...\nkubectl apply -f dev/manifests/webhook/\ndeployment.apps/simple-kubernetes-webhook created\nservice/simple-kubernetes-webhook created\nsecret/simple-kubernetes-webhook-tls created\n```\n\nThen, make sure the admission webhook pod is running (in the `default` namespace):\n```\n❯ kubectl get pods\nNAME                                        READY   STATUS    RESTARTS   AGE\nsimple-kubernetes-webhook-77444566b7-wzwmx   1/1     Running   0          2m21s\n```\n\nYou can stream logs from it:\n```\n❯ make logs\n\n🔍 Streaming simple-kubernetes-webhook logs...\nkubectl logs -l app=simple-kubernetes-webhook -f\ntime=\"2021-09-03T04:59:10Z\" level=info msg=\"Listening on port 443...\"\ntime=\"2021-09-03T05:02:21Z\" level=debug msg=healthy uri=/health\n```\n\nAnd hit it's health endpoint from your local machine:\n```\n❯ curl -k https://localhost:8443/health\nOK\n```\n\n### Deploying pods\nDeploy a valid test pod that gets succesfully created:\n```\n❯ make pod\n\n🚀 Deploying test pod...\nkubectl apply -f dev/manifests/pods/lifespan-seven.pod.yaml\npod/lifespan-seven created\n```\nYou should see in the admission webhook logs that the pod got mutated and validated.\n\nDeploy a non valid pod that gets rejected:\n```\n❯ make bad-pod\n\n🚀 Deploying \"bad\" pod...\nkubectl apply -f dev/manifests/pods/bad-name.pod.yaml\nError from server: error when creating \"dev/manifests/pods/bad-name.pod.yaml\": admission webhook \"simple-kubernetes-webhook.acme.com\" denied the request: pod name contains \"offensive\"\n```\nYou should see in the admission webhook logs that the pod validation failed. It's possible you will also see that the pod was mutated, as webhook configurations are not ordered.\n\n## Testing\nUnit tests can be run with the following command:\n```\n$ make test\ngo test ./...\n?   \tgithub.com/slackhq/simple-kubernetes-webhook\t[no test files]\nok  \tgithub.com/slackhq/simple-kubernetes-webhook/pkg/admission\t0.611s\nok  \tgithub.com/slackhq/simple-kubernetes-webhook/pkg/mutation\t1.064s\nok  \tgithub.com/slackhq/simple-kubernetes-webhook/pkg/validation\t0.749s\n```\n\n## Admission Logic\nA set of validations and mutations are implemented in an extensible framework. Those happen on the fly when a pod is deployed and no further resources are tracked and updated (ie. no controller logic).\n\n### Validating Webhooks\n#### Implemented\n- [name validation](pkg/validation/name_validator.go): validates that a pod name doesn't contain any offensive string\n\n#### How to add a new pod validation\nTo add a new pod mutation, create a file `pkg/validation/MUTATION_NAME.go`, then create a new struct implementing the `validation.podValidator` interface.\n\n### Mutating Webhooks\n#### Implemented\n- [inject env](pkg/mutation/inject_env.go): inject environment variables into the pod such as `KUBE: true`\n- [minimum pod lifespan](pkg/mutation/minimum_lifespan.go): inject a set of tolerations used to match pods to nodes of a certain age, the tolerations injected are controlled via the `acme.com/lifespan-requested` pod label.\n\n#### How to add a new pod mutation\nTo add a new pod mutation, create a file `pkg/mutation/MUTATION_NAME.go`, then create a new struct implementing the `mutation.podMutator` interface.\n\n\n\n"
  },
  {
    "path": "dev/gen-certs.sh",
    "content": "#!/bin/bash\n\nopenssl genrsa -out ca.key 2048\n\nopenssl req -new -x509 -days 365 -key ca.key \\\n  -subj \"/C=AU/CN=simple-kubernetes-webhook\"\\\n  -out ca.crt\n\nopenssl req -newkey rsa:2048 -nodes -keyout server.key \\\n  -subj \"/C=AU/CN=simple-kubernetes-webhook\" \\\n  -out server.csr\n\nopenssl x509 -req \\\n  -extfile <(printf \"subjectAltName=DNS:simple-kubernetes-webhook.default.svc\") \\\n  -days 365 \\\n  -in server.csr \\\n  -CA ca.crt -CAkey ca.key -CAcreateserial \\\n  -out server.crt\n\necho\necho \">> Generating kube secrets...\"\nkubectl create secret tls simple-kubernetes-webhook-tls \\\n  --cert=server.crt \\\n  --key=server.key \\\n  --dry-run=client -o yaml \\\n  > ./manifests/webhook/webhook.tls.secret.yaml\n\necho\necho \">> MutatingWebhookConfiguration caBundle:\"\ncat ca.crt | base64 | fold\n\nrm ca.crt ca.key ca.srl server.crt server.csr server.key\n"
  },
  {
    "path": "dev/manifests/cluster-config/apps.ns.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: apps\n  labels:\n    admission-webhook: enabled\n"
  },
  {
    "path": "dev/manifests/cluster-config/mutating.config.yaml",
    "content": "apiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: \"simple-kubernetes-webhook.acme.com\"\nwebhooks:\n  - name: \"simple-kubernetes-webhook.acme.com\"\n    namespaceSelector:\n      matchLabels:\n        admission-webhook: enabled\n    rules:\n      - apiGroups: [\"\"]\n        apiVersions: [\"v1\"]\n        operations: [\"CREATE\"]\n        resources: [\"pods\"]\n        scope: \"*\"\n    clientConfig:\n      service:\n        namespace: default\n        name: simple-kubernetes-webhook\n        path: /mutate-pods\n        port: 443\n      caBundle: |\n        LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzakNDQWNZQ0NRRFlHcU05a0ZZUjJqQU5CZ2tx\n        aGtpRzl3MEJBUXNGQURBeE1Rc3dDUVlEVlFRR0V3SkIKVlRFaU1DQUdBMVVFQXd3WmMybHRjR3hsTFd0\n        MVltVnlibVYwWlhNdGQyVmlhRzl2YXpBZUZ3MHlNVEV3TVRRdwpPREEyTkRCYUZ3MHlNakV3TVRRd09E\n        QTJOREJhTURFeEN6QUpCZ05WQkFZVEFrRlZNU0l3SUFZRFZRUUREQmx6CmFXMXdiR1V0YTNWaVpYSnVa\n        WFJsY3kxM1pXSm9iMjlyTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEEKTUlJQkNnS0NBUUVB\n        M1piR3NzSk9GZ2JkTlBDMUJjZVdaeGN4RDVoRkc0M0YxTXRwTXdzeDUrTFlJejQ3M0pPTgo0RGh6Snlr\n        V3huTVZEOEd4UElYYzNWUGNsVHp0V3dvdjdyOVo4dUxDRWdFakwyRWJFbjBKVzVTK2s2NkYwK0ZaCjI1\n        Y1lQNWVqMjVOd1Iwb3ZpbU9VZUpFelcyQktCT3ZGTTlPcmlhN0tkYkdRTWxRSkVFK3JMNXQxYWZmamhu\n        SVEKdk80MFZwblBFMkQvdmZzaTlEdmVyaTZFOFc2OWJxMEJ4NXRkZUZBalN1Q0FOWldLNjhjOEhIQ3Er\n        U3FjQ2ZaeAp5YVRmd09xQmsvYWkrMGE3a0RpUXRELzBiY0xyNkRnS3ZkckxRSmZveUlidHE1SklMamtu\n        U2VhNFJPazRMYS9xCmN3KytpNFZpVWtOS3pUSTVUWWV0c0NKWDFhZFdBMXYvQ3dJREFRQUJNQTBHQ1Nx\n        R1NJYjNEUUVCQ3dVQUE0SUIKQVFEWXMrNDRuWFc0STZLeSs2VGlGVjZveTErc3lMN2pFNlVONE1oM1JD\n        eWY4Y1Q0MEVBM3VEcTlZYjVmK3BySQpMbXZpd2RLbm1CbzhHR24zN1N1YWNtYmdMOUlxVlJUZ0hlSGZw\n        dElsblMwRklDNFVlM1hKOVRxSkNqbDBGbjgyCm9jK05FSytITjNkcldyMjMrdnZObnVlRzI4djhNenpD\n        V2JjZk9pd0I1TGQxZ0RDbEhIc2RhSHpJZFVjdkk1dGUKbFdzM3U0aXFyYkJDdWFUOWV6OUk5RTdqdHdr\n        R0hwVVpFV2tiNVhLcEt4SlNXQVRyWm5sTGRtTWxDb2FqM2grawpvbkNSd3R6L2d1aFc3dVJaWlQ4NGtE\n        MS9SWGo5d3VySE4zZ1NsVDAyVkhFeHpFUUoxM21aVS82V2p3dE05NWVmCmt6NzZiY2VoR05MU0hPU2lE\n        U1V5b0tBUQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n    admissionReviewVersions: [\"v1\"]\n    sideEffects: None\n    timeoutSeconds: 2\n"
  },
  {
    "path": "dev/manifests/cluster-config/validating.config.yaml",
    "content": "apiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: \"simple-kubernetes-webhook.acme.com\"\nwebhooks:\n  - name: \"simple-kubernetes-webhook.acme.com\"\n    namespaceSelector:\n      matchLabels:\n        admission-webhook: enabled\n    rules:\n      - apiGroups: [\"\"]\n        apiVersions: [\"v1\"]\n        operations: [\"CREATE\"]\n        resources: [\"pods\"]\n        scope: \"*\"\n    clientConfig:\n      service:\n        namespace: default\n        name: simple-kubernetes-webhook\n        path: /validate-pods\n        port: 443\n      caBundle: |\n        LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzakNDQWNZQ0NRRFlHcU05a0ZZUjJqQU5CZ2tx\n        aGtpRzl3MEJBUXNGQURBeE1Rc3dDUVlEVlFRR0V3SkIKVlRFaU1DQUdBMVVFQXd3WmMybHRjR3hsTFd0\n        MVltVnlibVYwWlhNdGQyVmlhRzl2YXpBZUZ3MHlNVEV3TVRRdwpPREEyTkRCYUZ3MHlNakV3TVRRd09E\n        QTJOREJhTURFeEN6QUpCZ05WQkFZVEFrRlZNU0l3SUFZRFZRUUREQmx6CmFXMXdiR1V0YTNWaVpYSnVa\n        WFJsY3kxM1pXSm9iMjlyTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEEKTUlJQkNnS0NBUUVB\n        M1piR3NzSk9GZ2JkTlBDMUJjZVdaeGN4RDVoRkc0M0YxTXRwTXdzeDUrTFlJejQ3M0pPTgo0RGh6Snlr\n        V3huTVZEOEd4UElYYzNWUGNsVHp0V3dvdjdyOVo4dUxDRWdFakwyRWJFbjBKVzVTK2s2NkYwK0ZaCjI1\n        Y1lQNWVqMjVOd1Iwb3ZpbU9VZUpFelcyQktCT3ZGTTlPcmlhN0tkYkdRTWxRSkVFK3JMNXQxYWZmamhu\n        SVEKdk80MFZwblBFMkQvdmZzaTlEdmVyaTZFOFc2OWJxMEJ4NXRkZUZBalN1Q0FOWldLNjhjOEhIQ3Er\n        U3FjQ2ZaeAp5YVRmd09xQmsvYWkrMGE3a0RpUXRELzBiY0xyNkRnS3ZkckxRSmZveUlidHE1SklMamtu\n        U2VhNFJPazRMYS9xCmN3KytpNFZpVWtOS3pUSTVUWWV0c0NKWDFhZFdBMXYvQ3dJREFRQUJNQTBHQ1Nx\n        R1NJYjNEUUVCQ3dVQUE0SUIKQVFEWXMrNDRuWFc0STZLeSs2VGlGVjZveTErc3lMN2pFNlVONE1oM1JD\n        eWY4Y1Q0MEVBM3VEcTlZYjVmK3BySQpMbXZpd2RLbm1CbzhHR24zN1N1YWNtYmdMOUlxVlJUZ0hlSGZw\n        dElsblMwRklDNFVlM1hKOVRxSkNqbDBGbjgyCm9jK05FSytITjNkcldyMjMrdnZObnVlRzI4djhNenpD\n        V2JjZk9pd0I1TGQxZ0RDbEhIc2RhSHpJZFVjdkk1dGUKbFdzM3U0aXFyYkJDdWFUOWV6OUk5RTdqdHdr\n        R0hwVVpFV2tiNVhLcEt4SlNXQVRyWm5sTGRtTWxDb2FqM2grawpvbkNSd3R6L2d1aFc3dVJaWlQ4NGtE\n        MS9SWGo5d3VySE4zZ1NsVDAyVkhFeHpFUUoxM21aVS82V2p3dE05NWVmCmt6NzZiY2VoR05MU0hPU2lE\n        U1V5b0tBUQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n    admissionReviewVersions: [\"v1\"]\n    sideEffects: None\n    timeoutSeconds: 2\n"
  },
  {
    "path": "dev/manifests/kind/kind.cluster.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n  - role: control-plane\n    image: kindest/node:v1.21.1\n    extraPortMappings:\n      - containerPort: 30100\n        hostPort: 8443\n        listenAddress: \"0.0.0.0\"\n        protocol: TCP\n"
  },
  {
    "path": "dev/manifests/pods/bad-name.pod.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: offensive-pod\n  namespace: apps\nspec:\n  containers:\n    - args:\n        - sleep\n        - \"3600\"\n      image: busybox\n      name: lifespan-offensive\n  restartPolicy: Always\n"
  },
  {
    "path": "dev/manifests/pods/lifespan-seven.pod.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    acme.com/lifespan-requested: \"7\"\n  name: lifespan-seven\n  namespace: apps\nspec:\n  containers:\n    - args:\n        - sleep\n        - \"3600\"\n      image: busybox\n      name: lifespan-seven\n  restartPolicy: Always\n"
  },
  {
    "path": "dev/manifests/pods/lifespan-three.pod.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    acme.com/lifespan-requested: \"3\"\n  name: lifespan-three\n  namespace: apps\nspec:\n  containers:\n    - args:\n        - sleep\n        - \"3600\"\n      image: busybox\n      name: lifespan-three\n  restartPolicy: Always\n"
  },
  {
    "path": "dev/manifests/pods/no-lifespan-label.deploy.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: deploy\n  name: deploy\n  namespace: apps\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: deploy\n  template:\n    metadata:\n      labels:\n        app: deploy\n    spec:\n      containers:\n        - command:\n            - sleep\n            - \"3600\"\n          image: busybox\n          name: busybox\n"
  },
  {
    "path": "dev/manifests/pods/no-lifespan-label.pod.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: no-labels\n  namespace: apps\nspec:\n  containers:\n    - args:\n        - sleep\n        - \"3600\"\n      image: busybox\n      name: no-labels\n  restartPolicy: Always\n"
  },
  {
    "path": "dev/manifests/webhook/webhook.deploy.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: simple-kubernetes-webhook\n  name: simple-kubernetes-webhook\n  namespace: default\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: simple-kubernetes-webhook\n  template:\n    metadata:\n      labels:\n        app: simple-kubernetes-webhook\n    spec:\n      tolerations:\n        - key: acme.com/lifespan-remaining\n          operator: Exists\n          effect: NoSchedule\n      containers:\n        - image: simple-kubernetes-webhook:latest\n          imagePullPolicy: Never\n          name: simple-kubernetes-webhook\n          env:\n            - name: TLS\n              value: \"true\"\n            - name: LOG_LEVEL\n              value: \"trace\"\n            - name: LOG_JSON\n              value: \"false\"\n          volumeMounts:\n            - name: tls\n              mountPath: \"/etc/admission-webhook/tls\"\n              readOnly: true\n      volumes:\n        - name: tls\n          secret:\n            secretName: simple-kubernetes-webhook-tls\n"
  },
  {
    "path": "dev/manifests/webhook/webhook.svc.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: simple-kubernetes-webhook\n  name: simple-kubernetes-webhook\n  namespace: default\nspec:\n  type: NodePort\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 443\n      nodePort: 30100\n  selector:\n    app: simple-kubernetes-webhook\n"
  },
  {
    "path": "dev/manifests/webhook/webhook.tls.secret.yaml",
    "content": "apiVersion: v1\ndata:\n  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lKQUxLcHI1S3RFeG5sTUEwR0NTcUdTSWIzRFFFQkJRVUFNREV4Q3pBSkJnTlYKQkFZVEFrRlZNU0l3SUFZRFZRUUREQmx6YVcxd2JHVXRhM1ZpWlhKdVpYUmxjeTEzWldKb2IyOXJNQjRYRFRJeApNVEF4TkRBNE1EWTBNRm9YRFRJeU1UQXhOREE0TURZME1Gb3dNVEVMTUFrR0ExVUVCaE1DUVZVeElqQWdCZ05WCkJBTU1HWE5wYlhCc1pTMXJkV0psY201bGRHVnpMWGRsWW1odmIyc3dnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUEKQTRJQkR3QXdnZ0VLQW9JQkFRREhvb0pCVGxuZTRYcE1sQWJWODBDeVJPMVJ3blM1ekFKY3BZdHMxNkc2NkpSSgpTTmJQQWt3YzY0dUNhYlJHNkVhM3Bva3dZVURETUxzQng0QVFUMHFrdUlJdFJYMFBCTjAzSmMxeFo0ZmtmekhICkMycG1tUFpnd3JGWGhOYUlNWlBBbTBqMnc0dmFPcFZObldKR3NRMkNiUUd2NGpWZS9DZHBkaXQxY3BRVWRTamsKZlFwMUJ1Mm95bFR3V1g1NXRjNXhRK0J2NDZVME5pK2c2elZHUUpmQm9JVGVTa3FqU1Q3SEtpM1F6NDBQUjJHRQpXZzZCaS9UWU5GclpxUFR4QmZkMnRMOWY2b1daQ1Q4MVFiS3FDeXB6RExJWkVoQktRSGNRTFpvMlJGNmlSWE9YCmZBY2tDOWNqcldsZ2Z3WHNyQVBHVkxTcVlyaGptK050b0svNFRTYUJBZ01CQUFHak5EQXlNREFHQTFVZEVRUXAKTUNlQ0pYTnBiWEJzWlMxcmRXSmxjbTVsZEdWekxYZGxZbWh2YjJzdVpHVm1ZWFZzZEM1emRtTXdEUVlKS29aSQpodmNOQVFFRkJRQURnZ0VCQURvSExWcGttR2d4NEZUekt1WXI4MGxjbUV2bVFBaG5GcWpWVjBEcmFoMGxId2NqClk1WVZPaWFYOGNBQ2lTYjFabFh4dVR2QzdQaE96SFg4MlphdjhES3Y5SzhkNjVuK3NZb1B4aFNpREdCOTZ3TUgKalB1OTcvck5VSWpjdGduZDlBZk8rZm8rQTJKRmltSWV6WFl2cGhuc2R0cXpxMzRwTkhLYnB6ZXJDZ3FmRkZpdwpaRngwd2svNkdVMlNkT2xxbXJxOEN0VkcwRVVDZG9Pd2xSL1RDUjgyUnQxbUUvSklhcWJ6eDc3dkg3bnk2L05ZCnNNVWgwbER0TVBTQVo2MkNLRnFTZnB4UTZlMXNlNmdVcFZZU2xMWDRZY3pxUy91YWR2VEQvR1lzQWhQbGd2YTYKRUxYaWwwck4wRDc3eTJ3bVMzb3JRK2hxUG9BM0Vja0N5Tm1ySFo0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRREhvb0pCVGxuZTRYcE0KbEFiVjgwQ3lSTzFSd25TNXpBSmNwWXRzMTZHNjZKUkpTTmJQQWt3YzY0dUNhYlJHNkVhM3Bva3dZVURETUxzQgp4NEFRVDBxa3VJSXRSWDBQQk4wM0pjMXhaNGZrZnpISEMycG1tUFpnd3JGWGhOYUlNWlBBbTBqMnc0dmFPcFZOCm5XSkdzUTJDYlFHdjRqVmUvQ2RwZGl0MWNwUVVkU2prZlFwMUJ1Mm95bFR3V1g1NXRjNXhRK0J2NDZVME5pK2cKNnpWR1FKZkJvSVRlU2txalNUN0hLaTNRejQwUFIyR0VXZzZCaS9UWU5GclpxUFR4QmZkMnRMOWY2b1daQ1Q4MQpRYktxQ3lwekRMSVpFaEJLUUhjUUxabzJSRjZpUlhPWGZBY2tDOWNqcldsZ2Z3WHNyQVBHVkxTcVlyaGptK050Cm9LLzRUU2FCQWdNQkFBRUNnZ0VBVWV4QVk2aFJmUU11ZXVwcis3UjlJaXJpOEtCSjRrenoweTBrRUNCVkFDeWQKWFkyRWlTSzZOVXY3emlLdWxrS1BjcUhtdm5IS2I4ODVqcnRkdEZPMW4rOFBqS0J0ZDVKWmJWNFg5cWV6dm5Mcgo3SENrMDBHR0thTDd2NXlGcFJJalBmRDdlamc0MWU4Z2dkOUtDeFJ4Sk1xeTNJaUp1bGJqbllXZXcrMm5FdFZlCnZZN2NjUlFZTWZMdjdROC90N0tQblBpR1FvbE93RFpuYUkvbWkvV2xZQk5TQ1o1Y0E0NXIveldQOGpxaVVLY0cKN1Jrb1F1WEZUQmJHcUVuU1hYOWZOaEcwOWpFQ09MK1gwd0J2VkF1SE9lamdNOS9HUHRyTnl1aW1QUTdSM2d6VwpwQ2tiRk9zR3o2aHcyM2Y5S0ZaOHJaRTBqRTIzWm43YTFjcVJGdklZZ1FLQmdRRDdtS0UrNnlKWjJzR3FQMmdFCk5mcThZUFg0TWQ1Z0xJQzlzRk05b1llU0h0OGZUV1lYa1ZDVURiM1dTVDRmekhDOFR1a2NMN3dZcU93bWlsRDIKOXUrUk40ZVp6RDEzU2ZHR3AxMmNUWVJkZ1QrbTI3M1B3aFNBNXFNNVZYR2xlVmY0aGdjY2UwbGErOUJWOXFiSAo3dmlSL1J2Z29iaVJjVG1YY1NBYldSYzJTUUtCZ1FETElRdnNJYnVGRzNmc25taU5FSmZESG1qeXMvRzVoc0ZpCnBMSm1jYmdKMDZNOWJQSEc0VFl1bFR5YWQ5WmdUWjY4VWIvZEt3MEd3Und6ZXk3WWJBZlpES2tjU1pUZnc1cnMKWG9YUE8yU1BCUzRzVnNoaGhLMkdxVUtpS1VvalV5cGxFZlEvZmJJRkd6K3F6VmpDZllZQ3h0TUlWY2lzSWZBTwoxTUhhT25pT2VRS0JnQW1JUDl1MVp1akdtLzNLUnpPWm8vVk5LeVNMSnlTM3F1MEU2REoya3o5YkFoTWFpSnF0Cis4S1FQcmdHc0Y3ZURRdGxaZm1XYVdiNXgzQ3lYdHpzZ0NrZFZIcmtQUlB1N2tLdXhxSXNZYTUxUGljaFBqREgKNXFUM21BbU5EakE1eDdaM3hYOHp3SlM4NDZqT0hvV0dyVTVDcTdLNERka2MxQlREeVhhZnlueFpBb0dBTmVDNwpEOVBXc0RTYjk0Z0F4VUhjYnlXV3dxRldBVmFyM3FVK3FJdUxQQmdGbVZwWE91QXJoZW1SbklzaXNvS0VFd0UvCitjTGNmcWtqK01lNG9qRHRWL1hTdVMwUEx0YnNOYnZRbENuMXZ6V3BqSnNzSlNtUytUL1Y2N3MxN2U2Mk5QNngKSVZJT3NPb01WaHFIYTNidDM3aXE2dkFOL1JJM1lVZXZiMW5JOWtrQ2dZQkplQ2ZMU1hUd2tqQkYrQTNqY2dhQQowV0ZYeHlobzVzYVg3NGhqeDVzRnRBY2U4YVpBTXpmYTlEN1B3OVBobUE3NWE2b09EV2hJQzZNaklXV1ZlL0VrCnpnckRxZGhpV1BmNFUzNEVpZkUxRy9SdDJyV2dJb2tSWWcxVnIxMW04RjM0a2tZZEVBUGhISDBmak82SFRlZzMKUUFvTGhESjZzZm9FMU94aDZsU2Z6Zz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K\nkind: Secret\nmetadata:\n  creationTimestamp: null\n  name: simple-kubernetes-webhook-tls\ntype: kubernetes.io/tls\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/slackhq/simple-kubernetes-webhook\n\ngo 1.16\n\nrequire (\n\tgithub.com/google/go-cmp v0.5.5 // indirect\n\tgithub.com/sirupsen/logrus v1.8.1\n\tgithub.com/stretchr/testify v1.6.1\n\tgithub.com/wI2L/jsondiff v0.1.0\n\tgolang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect\n\tgolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect\n\tgopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect\n\tk8s.io/api v0.21.3\n\tk8s.io/apimachinery v0.21.3\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\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/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=\ngithub.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=\ngithub.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=\ngithub.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=\ngithub.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=\ngithub.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=\ngithub.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=\ngithub.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=\ngithub.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=\ngithub.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/wI2L/jsondiff v0.1.0 h1:j8KVhKey+qbyBy3VL8l3ZrxE907DTVTXcV/BvEeQAeM=\ngithub.com/wI2L/jsondiff v0.1.0/go.mod h1:KGXeexPwd48QqbM0XI+cuQDiZXI4n2Ceqs/5z+7WE4s=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=\ngolang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nk8s.io/api v0.19.4/go.mod h1:SbtJ2aHCItirzdJ36YslycFNzWADYH3tgOhvBEFtZAk=\nk8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ=\nk8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=\nk8s.io/apimachinery v0.19.4/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=\nk8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII=\nk8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=\nk8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=\nk8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=\nk8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=\nk8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=\nk8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=\nsigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=\nsigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=\nsigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=\nsigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/simple-kubernetes-webhook/pkg/admission\"\n\tadmissionv1 \"k8s.io/api/admission/v1\"\n)\n\nfunc main() {\n\tsetLogger()\n\n\t// handle our core application\n\thttp.HandleFunc(\"/validate-pods\", ServeValidatePods)\n\thttp.HandleFunc(\"/mutate-pods\", ServeMutatePods)\n\thttp.HandleFunc(\"/health\", ServeHealth)\n\n\t// start the server\n\t// listens to clear text http on port 8080 unless TLS env var is set to \"true\"\n\tif os.Getenv(\"TLS\") == \"true\" {\n\t\tcert := \"/etc/admission-webhook/tls/tls.crt\"\n\t\tkey := \"/etc/admission-webhook/tls/tls.key\"\n\t\tlogrus.Print(\"Listening on port 443...\")\n\t\tlogrus.Fatal(http.ListenAndServeTLS(\":443\", cert, key, nil))\n\t} else {\n\t\tlogrus.Print(\"Listening on port 8080...\")\n\t\tlogrus.Fatal(http.ListenAndServe(\":8080\", nil))\n\t}\n}\n\n// ServeHealth returns 200 when things are good\nfunc ServeHealth(w http.ResponseWriter, r *http.Request) {\n\tlogrus.WithField(\"uri\", r.RequestURI).Debug(\"healthy\")\n\tfmt.Fprint(w, \"OK\")\n}\n\n// ServeValidatePods validates an admission request and then writes an admission\n// review to `w`\nfunc ServeValidatePods(w http.ResponseWriter, r *http.Request) {\n\tlogger := logrus.WithField(\"uri\", r.RequestURI)\n\tlogger.Debug(\"received validation request\")\n\n\tin, err := parseRequest(*r)\n\tif err != nil {\n\t\tlogger.Error(err)\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tadm := admission.Admitter{\n\t\tLogger:  logger,\n\t\tRequest: in.Request,\n\t}\n\n\tout, err := adm.ValidatePodReview()\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not generate admission response: %v\", err)\n\t\tlogger.Error(e)\n\t\thttp.Error(w, e, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjout, err := json.Marshal(out)\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not parse admission response: %v\", err)\n\t\tlogger.Error(e)\n\t\thttp.Error(w, e, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tlogger.Debug(\"sending response\")\n\tlogger.Debugf(\"%s\", jout)\n\tfmt.Fprintf(w, \"%s\", jout)\n}\n\n// ServeMutatePods returns an admission review with pod mutations as a json patch\n// in the review response\nfunc ServeMutatePods(w http.ResponseWriter, r *http.Request) {\n\tlogger := logrus.WithField(\"uri\", r.RequestURI)\n\tlogger.Debug(\"received mutation request\")\n\n\tin, err := parseRequest(*r)\n\tif err != nil {\n\t\tlogger.Error(err)\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tadm := admission.Admitter{\n\t\tLogger:  logger,\n\t\tRequest: in.Request,\n\t}\n\n\tout, err := adm.MutatePodReview()\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not generate admission response: %v\", err)\n\t\tlogger.Error(e)\n\t\thttp.Error(w, e, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjout, err := json.Marshal(out)\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not parse admission response: %v\", err)\n\t\tlogger.Error(e)\n\t\thttp.Error(w, e, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tlogger.Debug(\"sending response\")\n\tlogger.Debugf(\"%s\", jout)\n\tfmt.Fprintf(w, \"%s\", jout)\n}\n\n// setLogger sets the logger using env vars, it defaults to text logs on\n// debug level unless otherwise specified\nfunc setLogger() {\n\tlogrus.SetLevel(logrus.DebugLevel)\n\n\tlev := os.Getenv(\"LOG_LEVEL\")\n\tif lev != \"\" {\n\t\tllev, err := logrus.ParseLevel(lev)\n\t\tif err != nil {\n\t\t\tlogrus.Fatalf(\"cannot set LOG_LEVEL to %q\", lev)\n\t\t}\n\t\tlogrus.SetLevel(llev)\n\t}\n\n\tif os.Getenv(\"LOG_JSON\") == \"true\" {\n\t\tlogrus.SetFormatter(&logrus.JSONFormatter{})\n\t}\n}\n\n// parseRequest extracts an AdmissionReview from an http.Request if possible\nfunc parseRequest(r http.Request) (*admissionv1.AdmissionReview, error) {\n\tif r.Header.Get(\"Content-Type\") != \"application/json\" {\n\t\treturn nil, fmt.Errorf(\"Content-Type: %q should be %q\",\n\t\t\tr.Header.Get(\"Content-Type\"), \"application/json\")\n\t}\n\n\tbodybuf := new(bytes.Buffer)\n\tbodybuf.ReadFrom(r.Body)\n\tbody := bodybuf.Bytes()\n\n\tif len(body) == 0 {\n\t\treturn nil, fmt.Errorf(\"admission request body is empty\")\n\t}\n\n\tvar a admissionv1.AdmissionReview\n\n\tif err := json.Unmarshal(body, &a); err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse admission review request: %v\", err)\n\t}\n\n\tif a.Request == nil {\n\t\treturn nil, fmt.Errorf(\"admission review can't be used: Request field is nil\")\n\t}\n\n\treturn &a, nil\n}\n"
  },
  {
    "path": "pkg/admission/admission.go",
    "content": "// Package admission handles kubernetes admissions,\n// it takes admission requests and returns admission reviews;\n// for example, to mutate or validate pods\npackage admission\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/simple-kubernetes-webhook/pkg/mutation\"\n\t\"github.com/slackhq/simple-kubernetes-webhook/pkg/validation\"\n\tadmissionv1 \"k8s.io/api/admission/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n)\n\n// Admitter is a container for admission business\ntype Admitter struct {\n\tLogger  *logrus.Entry\n\tRequest *admissionv1.AdmissionRequest\n}\n\n// MutatePodReview takes an admission request and mutates the pod within,\n// it returns an admission review with mutations as a json patch (if any)\nfunc (a Admitter) MutatePodReview() (*admissionv1.AdmissionReview, error) {\n\tpod, err := a.Pod()\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not parse pod in admission review request: %v\", err)\n\t\treturn reviewResponse(a.Request.UID, false, http.StatusBadRequest, e), err\n\t}\n\n\tm := mutation.NewMutator(a.Logger)\n\tpatch, err := m.MutatePodPatch(pod)\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not mutate pod: %v\", err)\n\t\treturn reviewResponse(a.Request.UID, false, http.StatusBadRequest, e), err\n\t}\n\n\treturn patchReviewResponse(a.Request.UID, patch)\n}\n\n// MutatePodReview takes an admission request and validates the pod within\n// it returns an admission review\nfunc (a Admitter) ValidatePodReview() (*admissionv1.AdmissionReview, error) {\n\tpod, err := a.Pod()\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not parse pod in admission review request: %v\", err)\n\t\treturn reviewResponse(a.Request.UID, false, http.StatusBadRequest, e), err\n\t}\n\n\tv := validation.NewValidator(a.Logger)\n\tval, err := v.ValidatePod(pod)\n\tif err != nil {\n\t\te := fmt.Sprintf(\"could not validate pod: %v\", err)\n\t\treturn reviewResponse(a.Request.UID, false, http.StatusBadRequest, e), err\n\t}\n\n\tif !val.Valid {\n\t\treturn reviewResponse(a.Request.UID, false, http.StatusForbidden, val.Reason), nil\n\t}\n\n\treturn reviewResponse(a.Request.UID, true, http.StatusAccepted, \"valid pod\"), nil\n}\n\n// Pod extracts a pod from an admission request\nfunc (a Admitter) Pod() (*corev1.Pod, error) {\n\tif a.Request.Kind.Kind != \"Pod\" {\n\t\treturn nil, fmt.Errorf(\"only pods are supported here\")\n\t}\n\n\tp := corev1.Pod{}\n\tif err := json.Unmarshal(a.Request.Object.Raw, &p); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &p, nil\n}\n\n// reviewResponse TODO: godoc\nfunc reviewResponse(uid types.UID, allowed bool, httpCode int32,\n\treason string) *admissionv1.AdmissionReview {\n\treturn &admissionv1.AdmissionReview{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"AdmissionReview\",\n\t\t\tAPIVersion: \"admission.k8s.io/v1\",\n\t\t},\n\t\tResponse: &admissionv1.AdmissionResponse{\n\t\t\tUID:     uid,\n\t\t\tAllowed: allowed,\n\t\t\tResult: &metav1.Status{\n\t\t\t\tCode:    httpCode,\n\t\t\t\tMessage: reason,\n\t\t\t},\n\t\t},\n\t}\n}\n\n// patchReviewResponse builds an admission review with given json patch\nfunc patchReviewResponse(uid types.UID, patch []byte) (*admissionv1.AdmissionReview, error) {\n\tpatchType := admissionv1.PatchTypeJSONPatch\n\n\treturn &admissionv1.AdmissionReview{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"AdmissionReview\",\n\t\t\tAPIVersion: \"admission.k8s.io/v1\",\n\t\t},\n\t\tResponse: &admissionv1.AdmissionResponse{\n\t\t\tUID:       uid,\n\t\t\tAllowed:   true,\n\t\t\tPatchType: &patchType,\n\t\t\tPatch:     patch,\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/admission/admission_test.go",
    "content": "package admission\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tadmissionv1 \"k8s.io/api/admission/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n)\n\nfunc TestPod(t *testing.T) {\n\twant := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t},\n\t}\n\n\traw, err := json.Marshal(want)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tadmreq := &admissionv1.AdmissionRequest{\n\t\tUID:  types.UID(\"test\"),\n\t\tKind: metav1.GroupVersionKind{Group: \"\", Version: \"v1\", Kind: \"Pod\"},\n\t\tObject: runtime.RawExtension{\n\t\t\tRaw:    raw,\n\t\t\tObject: runtime.Object(nil),\n\t\t},\n\t}\n\n\ta := Admitter{Request: admreq}\n\tgot, err := a.Pod()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, want, got)\n}\n\nfunc TestReviewResponse(t *testing.T) {\n\tuid := types.UID(\"test\")\n\treason := \"fail!\"\n\n\twant := &admissionv1.AdmissionReview{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"AdmissionReview\",\n\t\t\tAPIVersion: \"admission.k8s.io/v1\",\n\t\t},\n\t\tResponse: &admissionv1.AdmissionResponse{\n\t\t\tUID:     uid,\n\t\t\tAllowed: false,\n\t\t\tResult: &metav1.Status{\n\t\t\t\tCode:    418,\n\t\t\t\tMessage: reason,\n\t\t\t},\n\t\t},\n\t}\n\n\tgot := reviewResponse(uid, false, http.StatusTeapot, reason)\n\tassert.Equal(t, want, got)\n}\n\nfunc TestPatchReviewResponse(t *testing.T) {\n\tuid := types.UID(\"test\")\n\tpatchType := admissionv1.PatchTypeJSONPatch\n\tpatch := []byte(`not quite a real patch`)\n\n\twant := &admissionv1.AdmissionReview{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"AdmissionReview\",\n\t\t\tAPIVersion: \"admission.k8s.io/v1\",\n\t\t},\n\t\tResponse: &admissionv1.AdmissionResponse{\n\t\t\tUID:       uid,\n\t\t\tAllowed:   true,\n\t\t\tPatchType: &patchType,\n\t\t\tPatch:     patch,\n\t\t},\n\t}\n\n\tgot, err := patchReviewResponse(uid, patch)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.Equal(t, want, got)\n}\n"
  },
  {
    "path": "pkg/mutation/inject_env.go",
    "content": "package mutation\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// injectEnv is a container for the mutation injecting environment vars\ntype injectEnv struct {\n\tLogger logrus.FieldLogger\n}\n\n// injectEnv implements the podMutator interface\nvar _ podMutator = (*injectEnv)(nil)\n\n// Name returns the struct name\nfunc (se injectEnv) Name() string {\n\treturn \"inject_env\"\n}\n\n// Mutate returns a new mutated pod according to set env rules\nfunc (se injectEnv) Mutate(pod *corev1.Pod) (*corev1.Pod, error) {\n\tse.Logger = se.Logger.WithField(\"mutation\", se.Name())\n\tmpod := pod.DeepCopy()\n\n\t// build out env var slice\n\tenvVars := []corev1.EnvVar{{\n\t\tName:  \"KUBE\",\n\t\tValue: \"true\",\n\t}}\n\n\t// inject env vars into pod\n\tfor _, envVar := range envVars {\n\t\tse.Logger.Debugf(\"pod env injected %s\", envVar)\n\t\tinjectEnvVar(mpod, envVar)\n\t}\n\n\treturn mpod, nil\n}\n\n// injectEnvVar injects a var in both containers and init containers of a pod\nfunc injectEnvVar(pod *corev1.Pod, envVar corev1.EnvVar) {\n\tfor i, container := range pod.Spec.Containers {\n\t\tif !HasEnvVar(container, envVar) {\n\t\t\tpod.Spec.Containers[i].Env = append(container.Env, envVar)\n\t\t}\n\t}\n\tfor i, container := range pod.Spec.InitContainers {\n\t\tif !HasEnvVar(container, envVar) {\n\t\t\tpod.Spec.InitContainers[i].Env = append(container.Env, envVar)\n\t\t}\n\t}\n}\n\n// HasEnvVar returns true if environment variable exists false otherwise\nfunc HasEnvVar(container corev1.Container, checkEnvVar corev1.EnvVar) bool {\n\tfor _, envVar := range container.Env {\n\t\tif envVar.Name == checkEnvVar.Name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/mutation/inject_env_test.go",
    "content": "package mutation\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestInjectEnvMutate(t *testing.T) {\n\twant := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"test\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName: \"test\",\n\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"KUBE\",\n\t\t\t\t\t\tValue: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}},\n\t\t\tInitContainers: []corev1.Container{{\n\t\t\t\tName: \"inittest\",\n\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"KUBE\",\n\t\t\t\t\t\tValue: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}},\n\t\t},\n\t}\n\n\tpod := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"test\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName: \"test\",\n\t\t\t}},\n\t\t\tInitContainers: []corev1.Container{{\n\t\t\t\tName: \"inittest\",\n\t\t\t}},\n\t\t},\n\t}\n\n\tgot, err := injectEnv{Logger: logger()}.Mutate(pod)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, want, got)\n}\n\nfunc TestHasEnvVar(t *testing.T) {\n\tey := corev1.EnvVar{\n\t\tName:  \"foo\",\n\t\tValue: \"sball\",\n\t}\n\n\ten := corev1.EnvVar{\n\t\tName:  \"the_pope\",\n\t\tValue: \"of_nope\",\n\t}\n\n\tc := corev1.Container{\n\t\tName: \"test\",\n\t\tEnv:  []corev1.EnvVar{ey},\n\t}\n\n\tassert.True(t, HasEnvVar(c, ey))\n\tassert.False(t, HasEnvVar(c, en))\n}\n"
  },
  {
    "path": "pkg/mutation/minimum_lifespan.go",
    "content": "package mutation\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\n\t\"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// minLifespanTolerations is a container for mininum lifespan mutation\ntype minLifespanTolerations struct {\n\tLogger logrus.FieldLogger\n}\n\n// minLifespanTolerations implements the podMutator interface\nvar _ podMutator = (*minLifespanTolerations)(nil)\n\n// Name returns the minLifespanTolerations short name\nfunc (mpl minLifespanTolerations) Name() string {\n\treturn \"min_lifespan\"\n}\n\n// Mutate returns a new mutated pod according to lifespan tolerations rules\nfunc (mpl minLifespanTolerations) Mutate(pod *corev1.Pod) (*corev1.Pod, error) {\n\tconst (\n\t\tlifespanLabel = \"acme.com/lifespan-requested\"\n\t\ttaintKey      = \"acme.com/lifespan-remaining\"\n\t\ttaintMaxAge   = 14\n\t)\n\n\tmpl.Logger = mpl.Logger.WithField(\"mutation\", mpl.Name())\n\tmpod := pod.DeepCopy()\n\n\tif pod.Labels == nil || pod.Labels[lifespanLabel] == \"\" {\n\t\tmpl.Logger.WithField(\"min_lifespan\", 0).\n\t\t\tPrintf(\"no lifespan label found, applying default lifespan toleration\")\n\n\t\ttn := []corev1.Toleration{{\n\t\t\tKey:      taintKey,\n\t\t\tOperator: corev1.TolerationOpExists,\n\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t}}\n\n\t\tmpod.Spec.Tolerations = appendTolerations(tn, mpod.Spec.Tolerations)\n\t\treturn mpod, nil\n\t}\n\n\tts := pod.Labels[lifespanLabel]\n\tminAge, err := strconv.Atoi(ts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"pod lifespan label %q is not an integer: %v\", ts, err)\n\t}\n\n\tmpl.Logger.WithField(\"min_lifespan\", ts).Printf(\"setting lifespan tolerations\")\n\n\tt := []corev1.Toleration{}\n\tfor i := taintMaxAge; i >= minAge; i-- {\n\t\tt = append(t, corev1.Toleration{\n\t\t\tKey:      taintKey,\n\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\tValue:    fmt.Sprint(i),\n\t\t})\n\t}\n\n\tmpod.Spec.Tolerations = appendTolerations(t, mpod.Spec.Tolerations)\n\treturn mpod, nil\n}\n\n// appendTolerations appends existing to new without duplicating any tolerations\nfunc appendTolerations(new, existing []corev1.Toleration) []corev1.Toleration {\n\tvar toAppend []corev1.Toleration\n\n\tfor _, n := range new {\n\t\tfound := false\n\t\tfor _, e := range existing {\n\t\t\tif reflect.DeepEqual(n, e) {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\ttoAppend = append(toAppend, n)\n\t\t}\n\t}\n\n\treturn append(existing, toAppend...)\n}\n"
  },
  {
    "path": "pkg/mutation/minimum_lifespan_test.go",
    "content": "package mutation\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestMinLifespanTolerationsNoLabel(t *testing.T) {\n\twant := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t\tTolerations: []corev1.Toleration{\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpExists,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpod := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t},\n\t}\n\n\tgot, err := minLifespanTolerations{logger()}.Mutate(pod)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, want, got)\n}\n\nfunc TestMinLifespanTolerationsLabel(t *testing.T) {\n\twant := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"acme.com/lifespan-requested\": \"7\",\n\t\t\t},\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t\tTolerations: []corev1.Toleration{\n\t\t\t\t{\n\t\t\t\t\tKey:      \"something-unrelated\",\n\t\t\t\t\tOperator: corev1.TolerationOpExists,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"14\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"13\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"12\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"11\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"10\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"9\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"8\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpEqual,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t\tValue:    \"7\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpod := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"acme.com/lifespan-requested\": \"7\",\n\t\t\t},\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t\tTolerations: []corev1.Toleration{\n\t\t\t\t{\n\t\t\t\t\tKey:      \"something-unrelated\",\n\t\t\t\t\tOperator: corev1.TolerationOpExists,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tgot, err := minLifespanTolerations{logger()}.Mutate(pod)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.Equal(t, want, got)\n}\n\nfunc TestMinLifespanTolerationsIdempotence(t *testing.T) {\n\twant := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t\tTolerations: []corev1.Toleration{\n\t\t\t\t{\n\t\t\t\t\tKey:      \"acme.com/lifespan-remaining\",\n\t\t\t\t\tOperator: corev1.TolerationOpExists,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"something-unrelated\",\n\t\t\t\t\tOperator: corev1.TolerationOpExists,\n\t\t\t\t\tEffect:   corev1.TaintEffectNoSchedule,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tgot, err := minLifespanTolerations{logger()}.Mutate(want.DeepCopy())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, want, got)\n}\n"
  },
  {
    "path": "pkg/mutation/mutation.go",
    "content": "package mutation\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/wI2L/jsondiff\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// Mutator is a container for mutation\ntype Mutator struct {\n\tLogger *logrus.Entry\n}\n\n// NewMutator returns an initialised instance of Mutator\nfunc NewMutator(logger *logrus.Entry) *Mutator {\n\treturn &Mutator{Logger: logger}\n}\n\n// podMutators is an interface used to group functions mutating pods\ntype podMutator interface {\n\tMutate(*corev1.Pod) (*corev1.Pod, error)\n\tName() string\n}\n\n// MutatePodPatch returns a json patch containing all the mutations needed for\n// a given pod\nfunc (m *Mutator) MutatePodPatch(pod *corev1.Pod) ([]byte, error) {\n\tvar podName string\n\tif pod.Name != \"\" {\n\t\tpodName = pod.Name\n\t} else {\n\t\tif pod.ObjectMeta.GenerateName != \"\" {\n\t\t\tpodName = pod.ObjectMeta.GenerateName\n\t\t}\n\t}\n\tlog := logrus.WithField(\"pod_name\", podName)\n\n\t// list of all mutations to be applied to the pod\n\tmutations := []podMutator{\n\t\tminLifespanTolerations{Logger: log},\n\t\tinjectEnv{Logger: log},\n\t}\n\n\tmpod := pod.DeepCopy()\n\n\t// apply all mutations\n\tfor _, m := range mutations {\n\t\tvar err error\n\t\tmpod, err = m.Mutate(mpod)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// generate json patch\n\tpatch, err := jsondiff.Compare(pod, mpod)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpatchb, err := json.Marshal(patch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn patchb, nil\n}\n"
  },
  {
    "path": "pkg/mutation/mutation_test.go",
    "content": "package mutation\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/stretchr/testify/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestMutatePodPatch(t *testing.T) {\n\tm := NewMutator(logger())\n\tgot, err := m.MutatePodPatch(pod())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tp := patch()\n\tg := string(got)\n\tassert.Equal(t, p, g)\n}\n\nfunc BenchmarkMutatePodPatch(b *testing.B) {\n\tm := NewMutator(logger())\n\tpod := pod()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := m.MutatePodPatch(pod)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc pod() *corev1.Pod {\n\treturn &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"acme.com/lifespan-requested\": \"7\",\n\t\t\t},\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t},\n\t}\n}\n\nfunc patch() string {\n\tpatch := `[\n\t\t{\"op\":\"add\",\"path\":\"/spec/containers/0/env\",\"value\":[\n\t\t\t{\"name\":\"KUBE\",\"value\":\"true\"}\n\t\t]},\n\t\t{\"op\":\"add\",\"path\":\"/spec/tolerations\",\"value\":[\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"14\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"13\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"12\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"11\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"10\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"9\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"8\"},\n\t\t\t{\"effect\":\"NoSchedule\",\"key\":\"acme.com/lifespan-remaining\",\"operator\":\"Equal\",\"value\":\"7\"}\n\t\t]}\n]`\n\n\tpatch = strings.ReplaceAll(patch, \"\\n\", \"\")\n\tpatch = strings.ReplaceAll(patch, \"\\t\", \"\")\n\tpatch = strings.ReplaceAll(patch, \" \", \"\")\n\n\treturn patch\n}\n\nfunc logger() *logrus.Entry {\n\tmute := logrus.StandardLogger()\n\tmute.Out = ioutil.Discard\n\treturn mute.WithField(\"logger\", \"test\")\n}\n"
  },
  {
    "path": "pkg/validation/name_validator.go",
    "content": "package validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// nameValidator is a container for validating the name of pods\ntype nameValidator struct {\n\tLogger logrus.FieldLogger\n}\n\n// nameValidator implements the podValidator interface\nvar _ podValidator = (*nameValidator)(nil)\n\n// Name returns the name of nameValidator\nfunc (n nameValidator) Name() string {\n\treturn \"name_validator\"\n}\n\n// Validate inspects the name of a given pod and returns validation.\n// The returned validation is only valid if the pod name does not contain some\n// bad string.\nfunc (n nameValidator) Validate(pod *corev1.Pod) (validation, error) {\n\tbadString := \"offensive\"\n\n\tif strings.Contains(pod.Name, badString) {\n\t\tv := validation{\n\t\t\tValid:  false,\n\t\t\tReason: fmt.Sprintf(\"pod name contains %q\", badString),\n\t\t}\n\t\treturn v, nil\n\t}\n\n\treturn validation{Valid: true, Reason: \"valid name\"}, nil\n}\n"
  },
  {
    "path": "pkg/validation/name_validator_test.go",
    "content": "package validation\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestNameValidatorValidate(t *testing.T) {\n\tt.Run(\"good name\", func(t *testing.T) {\n\t\tpod := &corev1.Pod{\n\t\t\tObjectMeta: v1.ObjectMeta{\n\t\t\t\tName: \"lifespan\",\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\tName:  \"lifespan\",\n\t\t\t\t\tImage: \"busybox\",\n\t\t\t\t}},\n\t\t\t},\n\t\t}\n\n\t\tv, err := nameValidator{logger()}.Validate(pod)\n\t\tassert.Nil(t, err)\n\t\tassert.True(t, v.Valid)\n\t})\n\n\tt.Run(\"bad name\", func(t *testing.T) {\n\t\tpod := &corev1.Pod{\n\t\t\tObjectMeta: v1.ObjectMeta{\n\t\t\t\tName: \"lifespan-offensive\",\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\tName:  \"lifespan\",\n\t\t\t\t\tImage: \"busybox\",\n\t\t\t\t}},\n\t\t\t},\n\t\t}\n\n\t\tv, err := nameValidator{logger()}.Validate(pod)\n\t\tassert.Nil(t, err)\n\t\tassert.False(t, v.Valid)\n\t})\n}\n"
  },
  {
    "path": "pkg/validation/validation.go",
    "content": "package validation\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// Validator is a container for mutation\ntype Validator struct {\n\tLogger *logrus.Entry\n}\n\n// NewValidator returns an initialised instance of Validator\nfunc NewValidator(logger *logrus.Entry) *Validator {\n\treturn &Validator{Logger: logger}\n}\n\n// podValidators is an interface used to group functions mutating pods\ntype podValidator interface {\n\tValidate(*corev1.Pod) (validation, error)\n\tName() string\n}\n\ntype validation struct {\n\tValid  bool\n\tReason string\n}\n\n// ValidatePod returns true if a pod is valid\nfunc (v *Validator) ValidatePod(pod *corev1.Pod) (validation, error) {\n\tvar podName string\n\tif pod.Name != \"\" {\n\t\tpodName = pod.Name\n\t} else {\n\t\tif pod.ObjectMeta.GenerateName != \"\" {\n\t\t\tpodName = pod.ObjectMeta.GenerateName\n\t\t}\n\t}\n\tlog := logrus.WithField(\"pod_name\", podName)\n\tlog.Print(\"delete me\")\n\n\t// list of all validations to be applied to the pod\n\tvalidations := []podValidator{\n\t\tnameValidator{v.Logger},\n\t}\n\n\t// apply all validations\n\tfor _, v := range validations {\n\t\tvar err error\n\t\tvp, err := v.Validate(pod)\n\t\tif err != nil {\n\t\t\treturn validation{Valid: false, Reason: err.Error()}, err\n\t\t}\n\t\tif !vp.Valid {\n\t\t\treturn validation{Valid: false, Reason: vp.Reason}, err\n\t\t}\n\t}\n\n\treturn validation{Valid: true, Reason: \"valid pod\"}, nil\n}\n"
  },
  {
    "path": "pkg/validation/validation_test.go",
    "content": "package validation\n\nimport (\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/stretchr/testify/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestValidatePod(t *testing.T) {\n\tv := NewValidator(logger())\n\n\tpod := &corev1.Pod{\n\t\tObjectMeta: v1.ObjectMeta{\n\t\t\tName: \"lifespan\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tContainers: []corev1.Container{{\n\t\t\t\tName:  \"lifespan\",\n\t\t\t\tImage: \"busybox\",\n\t\t\t}},\n\t\t},\n\t}\n\n\tval, err := v.ValidatePod(pod)\n\tassert.Nil(t, err)\n\tassert.True(t, val.Valid)\n}\n\nfunc logger() *logrus.Entry {\n\tmute := logrus.StandardLogger()\n\tmute.Out = ioutil.Discard\n\treturn mute.WithField(\"logger\", \"test\")\n}\n"
  }
]