[
  {
    "path": ".dockerignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\nvendor/*\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\nvendor/*\n\nbuild/_output\n\n# GOPATH\n.go"
  },
  {
    "path": "Dockerfile",
    "content": "# Build the sidecar-injector binary\nFROM golang:1.17 as builder\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the go source\nCOPY cmd/ cmd/\n\n# Build\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o sidecar-injector ./cmd\n\n\nFROM alpine:latest\n\n# install curl for prestop script\nRUN apk --no-cache add curl\n\nWORKDIR /\n\n# install binary\nCOPY --from=builder /workspace/sidecar-injector .\n\n# install the prestop script\nCOPY ./prestop.sh .\n\nUSER 65532:65532\n\nENTRYPOINT [\"/sidecar-injector\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "# Setting SHELL to bash allows bash commands to be executed by recipes.\n# This is a requirement for 'setup-envtest.sh' in the test target.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# Tools for deploy\nKUBECTL?=kubectl\nPWD=$(shell pwd)\nKUSTOMIZE?=$(PWD)/$(PERMANENT_TMP_GOPATH)/bin/kustomize\nKUSTOMIZE_VERSION?=v3.5.4\nKUSTOMIZE_ARCHIVE_NAME?=kustomize_$(KUSTOMIZE_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH).tar.gz\nkustomize_dir:=$(dir $(KUSTOMIZE))\n\nIMAGE = quay.io/morvencao/sidecar-injector:latest\n\nall: build\n.PHONY: all\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk commands is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: fmt vet ## Run tests.\n\tgo test ./... -coverprofile cover.out\n\n##@ Build\n\n.PHONY: build\nbuild: fmt vet ## Build binary.\n\tgo build -o bin/sidecar-injector ./cmd/\n\n.PHONY: docker-build\ndocker-build: test ## Build docker image.\n\tdocker build -t ${IMAGE} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image.\n\tdocker push ${IMAGE}\n\n##@ Deployment\n\ndeploy: kustomize\n\tcp deploy/kustomization.yaml deploy/kustomization.yaml.tmp\n\tcd deploy && $(KUSTOMIZE) edit set image sidecar-injector=$(IMAGE)\n\t$(KUSTOMIZE) build deploy | $(KUBECTL) apply -f -\n\tmv deploy/kustomization.yaml.tmp deploy/kustomization.yaml\n\nundeploy: kustomize\n\t$(KUSTOMIZE) build deploy | $(KUBECTL) delete --ignore-not-found -f -\n\nKUSTOMIZE = $(shell pwd)/bin/kustomize\n.PHONY: kustomize\nkustomize: ## Download kustomize locally if necessary.\n\t$(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)\n\n# go-get-tool will 'go get' any package $2 and install it to $1.\nPROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))\ndefine go-get-tool\n@[ -f $(1) ] || { \\\nset -e ;\\\nTMP_DIR=$$(mktemp -d) ;\\\ncd $$TMP_DIR ;\\\ngo mod init tmp ;\\\necho \"Downloading $(2)\" ;\\\nGOBIN=$(PROJECT_DIR)/bin go get $(2) ;\\\nrm -rf $$TMP_DIR ;\\\n}\nendef\n"
  },
  {
    "path": "README.md",
    "content": "# kube-sidecar-injector\n\nThis repo is used for [a tutorial at Medium](https://medium.com/ibm-cloud/diving-into-kubernetes-mutatingadmissionwebhook-6ef3c5695f74) to create a Kubernetes [MutatingAdmissionWebhook](https://kubernetes.io/docs/admin/admission-controllers/#mutatingadmissionwebhook-beta-in-19) that injects a nginx sidecar container into pod prior to persistence of the object.\n\n## Prerequisites\n\n- [git](https://git-scm.com/downloads)\n- [go](https://golang.org/dl/) version v1.17+\n- [docker](https://docs.docker.com/install/) version 19.03+\n- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) version v1.19+\n- Access to a Kubernetes v1.19+ cluster with the `admissionregistration.k8s.io/v1` API enabled. Verify that by the following command:\n\n```\nkubectl api-versions | grep admissionregistration.k8s.io\n```\nThe result should be:\n```\nadmissionregistration.k8s.io/v1\nadmissionregistration.k8s.io/v1beta1\n```\n\n> Note: In addition, the `MutatingAdmissionWebhook` and `ValidatingAdmissionWebhook` admission controllers should be added and listed in the correct order in the admission-control flag of kube-apiserver.\n\n## Build and Deploy\n\n1. Build and push docker image:\n\n```bash\nmake docker-build docker-push IMAGE=quay.io/<your_quayio_username>/sidecar-injector:latest\n```\n\n2. Deploy the kube-sidecar-injector to kubernetes cluster:\n\n```bash\nmake deploy IMAGE=quay.io/<your_quayio_username>/sidecar-injector:latest\n```\n\n3. Verify the kube-sidecar-injector is up and running:\n\n```bash\n# kubectl -n sidecar-injector get pod\n# kubectl -n sidecar-injector get pod\nNAME                                READY   STATUS    RESTARTS   AGE\nsidecar-injector-7c8bc5f4c9-28c84   1/1     Running   0          30s\n```\n\n## How to use\n\n1. Create a new namespace `test-ns` and label it with `sidecar-injector=enabled`:\n\n```\n# kubectl create ns test-ns\n# kubectl label namespace test-ns sidecar-injection=enabled\n# kubectl get namespace -L sidecar-injection\nNAME                 STATUS   AGE   SIDECAR-INJECTION\ndefault              Active   26m\ntest-ns              Active   13s   enabled\nkube-public          Active   26m\nkube-system          Active   26m\nsidecar-injector     Active   17m\n```\n\n2. Deploy an app in Kubernetes cluster, take `alpine` app as an example\n\n```bash\nkubectl -n test-ns run alpine \\\n    --image=alpine \\\n    --restart=Never \\\n    --command -- sleep infinity\n```\n\n3. Verify sidecar container is injected:\n\n```\n# kubectl -n test-ns get pod\nNAME                     READY     STATUS        RESTARTS   AGE\nalpine                   2/2       Running       0          10s\n# kubectl -n test-ns get pod alpine -o jsonpath=\"{.spec.containers[*].name}\"\nalpine sidecar-nginx\n```\n\n## Troubleshooting\n\nSometimes you may find that pod is injected with sidecar container as expected, check the following items:\n\n1. The sidecar-injector pod is in running state and no error logs.\n2. The namespace in which application pod is deployed has the correct labels(`sidecar-injector=enabled`) as configured in `mutatingwebhookconfiguration`.\n3. Check if the application pod has annotation `sidecar-injector-webhook.morven.me/inject:\"yes\"`.\n"
  },
  {
    "path": "cmd/cert.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"time\"\n)\n\n// generateCert generate a self-signed CA for given organization\n// and sign certificate with the CA for given common name and dns names\n// it resurns the CA, certificate and private key in PEM format\nfunc generateCert(orgs, dnsNames []string, commonName string) (*bytes.Buffer, *bytes.Buffer, *bytes.Buffer, error) {\n\t// init CA config\n\tca := &x509.Certificate{\n\t\tSerialNumber:          big.NewInt(2022),\n\t\tSubject:               pkix.Name{Organization: orgs},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().AddDate(1, 0, 0), // expired in 1 year\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t}\n\n\t// generate private key for CA\n\tcaPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\t// create the CA certificate\n\tcaBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivateKey.PublicKey, caPrivateKey)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\t// CA certificate with PEM encoded\n\tcaPEM := new(bytes.Buffer)\n\t_ = pem.Encode(caPEM, &pem.Block{\n\t\tType:  \"CERTIFICATE\",\n\t\tBytes: caBytes,\n\t})\n\n\t// new certificate config\n\tnewCert := &x509.Certificate{\n\t\tDNSNames:     dnsNames,\n\t\tSerialNumber: big.NewInt(1024),\n\t\tSubject: pkix.Name{\n\t\t\tCommonName:   commonName,\n\t\t\tOrganization: orgs,\n\t\t},\n\t\tNotBefore:   time.Now(),\n\t\tNotAfter:    time.Now().AddDate(1, 0, 0), // expired in 1 year\n\t\tExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:    x509.KeyUsageDigitalSignature,\n\t}\n\n\t// generate new private key\n\tnewPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\t// sign the new certificate\n\tnewCertBytes, err := x509.CreateCertificate(rand.Reader, newCert, ca, &newPrivateKey.PublicKey, caPrivateKey)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\t// new certificate with PEM encoded\n\tnewCertPEM := new(bytes.Buffer)\n\t_ = pem.Encode(newCertPEM, &pem.Block{\n\t\tType:  \"CERTIFICATE\",\n\t\tBytes: newCertBytes,\n\t})\n\n\t// new private key with PEM encoded\n\tnewPrivateKeyPEM := new(bytes.Buffer)\n\t_ = pem.Encode(newPrivateKeyPEM, &pem.Block{\n\t\tType:  \"RSA PRIVATE KEY\",\n\t\tBytes: x509.MarshalPKCS1PrivateKey(newPrivateKey),\n\t})\n\n\treturn caPEM, newCertPEM, newPrivateKeyPEM, nil\n}\n"
  },
  {
    "path": "cmd/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nvar (\n\tinfoLogger    *log.Logger\n\twarningLogger *log.Logger\n\terrorLogger   *log.Logger\n)\n\nvar (\n\tport                                 int\n\tsidecarConfigFile                    string\n\twebhookNamespace, webhookServiceName string\n)\n\nfunc init() {\n\t// init loggers\n\tinfoLogger = log.New(os.Stderr, \"INFO: \", log.Ldate|log.Ltime|log.Lshortfile)\n\twarningLogger = log.New(os.Stderr, \"WARNING: \", log.Ldate|log.Ltime|log.Lshortfile)\n\terrorLogger = log.New(os.Stderr, \"ERROR: \", log.Ldate|log.Ltime|log.Lshortfile)\n\n\t// webhook server running namespace\n\twebhookNamespace = os.Getenv(\"POD_NAMESPACE\")\n}\n\nfunc main() {\n\t// init command flags\n\tflag.IntVar(&port, \"port\", 8443, \"Webhook server port.\")\n\tflag.StringVar(&webhookServiceName, \"service-name\", \"sidecar-injector\", \"Webhook service name.\")\n\tflag.StringVar(&sidecarConfigFile, \"sidecar-config-file\", \"/etc/webhook/config/sidecarconfig.yaml\", \"Sidecar injector configuration file.\")\n\t// flag.StringVar(&certFile, \"tlsCertFile\", \"/etc/webhook/certs/cert.pem\", \"x509 Certificate file.\")\n\t// flag.StringVar(&keyFile, \"tlsKeyFile\", \"/etc/webhook/certs/key.pem\", \"x509 private key file.\")\n\tflag.Parse()\n\n\tdnsNames := []string{\n\t\twebhookServiceName,\n\t\twebhookServiceName + \".\" + webhookNamespace,\n\t\twebhookServiceName + \".\" + webhookNamespace + \".svc\",\n\t}\n\tcommonName := webhookServiceName + \".\" + webhookNamespace + \".svc\"\n\n\torg := \"morven.me\"\n\tcaPEM, certPEM, certKeyPEM, err := generateCert([]string{org}, dnsNames, commonName)\n\tif err != nil {\n\t\terrorLogger.Fatalf(\"Failed to generate ca and certificate key pair: %v\", err)\n\t}\n\n\tpair, err := tls.X509KeyPair(certPEM.Bytes(), certKeyPEM.Bytes())\n\tif err != nil {\n\t\terrorLogger.Fatalf(\"Failed to load certificate key pair: %v\", err)\n\t}\n\n\tsidecarConfig, err := loadConfig(sidecarConfigFile)\n\tif err != nil {\n\t\terrorLogger.Fatalf(\"Failed to load configuration: %v\", err)\n\t}\n\n\t// create or update the mutatingwebhookconfiguration\n\terr = createOrUpdateMutatingWebhookConfiguration(caPEM, webhookServiceName, webhookNamespace)\n\tif err != nil {\n\t\terrorLogger.Fatalf(\"Failed to create or update the mutating webhook configuration: %v\", err)\n\t}\n\n\twhsvr := &WebhookServer{\n\t\tsidecarConfig: sidecarConfig,\n\t\tserver: &http.Server{\n\t\t\tAddr:      fmt.Sprintf(\":%v\", port),\n\t\t\tTLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}},\n\t\t},\n\t}\n\n\t// define http server and server handler\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(webhookInjectPath, whsvr.serve)\n\twhsvr.server.Handler = mux\n\n\t// start webhook server in new rountine\n\tgo func() {\n\t\tif err := whsvr.server.ListenAndServeTLS(\"\", \"\"); err != nil {\n\t\t\terrorLogger.Fatalf(\"Failed to listen and serve webhook server: %v\", err)\n\t\t}\n\t}()\n\n\t// listening OS shutdown singal\n\tsignalChan := make(chan os.Signal, 1)\n\tsignal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)\n\t<-signalChan\n\n\tinfoLogger.Printf(\"Got OS shutdown signal, shutting down webhook server gracefully...\")\n\twhsvr.server.Shutdown(context.Background())\n}\n"
  },
  {
    "path": "cmd/webhook.go",
    "content": "package main\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"gopkg.in/yaml.v2\"\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/runtime/serializer\"\n)\n\nvar (\n\truntimeScheme = runtime.NewScheme()\n\tcodecs        = serializer.NewCodecFactory(runtimeScheme)\n\tdeserializer  = codecs.UniversalDeserializer()\n)\n\nvar ignoredNamespaces = []string{\n\tmetav1.NamespaceSystem,\n\tmetav1.NamespacePublic,\n}\n\nconst (\n\tadmissionWebhookAnnotationInjectKey = \"sidecar-injector-webhook.morven.me/inject\"\n\tadmissionWebhookAnnotationStatusKey = \"sidecar-injector-webhook.morven.me/status\"\n)\n\ntype WebhookServer struct {\n\tsidecarConfig *Config\n\tserver        *http.Server\n}\n\n// Webhook Server parameters\ntype WhSvrParameters struct {\n\tport           int    // webhook server port\n\tcertFile       string // path to the x509 certificate for https\n\tkeyFile        string // path to the x509 private key matching `CertFile`\n\tsidecarCfgFile string // path to sidecar injector configuration file\n}\n\ntype Config struct {\n\tContainers []corev1.Container `yaml:\"containers\"`\n\tVolumes    []corev1.Volume    `yaml:\"volumes\"`\n}\n\ntype patchOperation struct {\n\tOp    string      `json:\"op\"`\n\tPath  string      `json:\"path\"`\n\tValue interface{} `json:\"value,omitempty\"`\n}\n\nfunc loadConfig(configFile string) (*Config, error) {\n\tdata, err := ioutil.ReadFile(configFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfoLogger.Printf(\"New configuration: sha256sum %x\", sha256.Sum256(data))\n\n\tvar cfg Config\n\tif err := yaml.Unmarshal(data, &cfg); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &cfg, nil\n}\n\n// Check whether the target resoured need to be mutated\nfunc mutationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool {\n\t// skip special kubernete system namespaces\n\tfor _, namespace := range ignoredList {\n\t\tif metadata.Namespace == namespace {\n\t\t\tinfoLogger.Printf(\"Skip mutation for %v for it's in special namespace:%v\", metadata.Name, metadata.Namespace)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tannotations := metadata.GetAnnotations()\n\tif annotations == nil {\n\t\tannotations = map[string]string{}\n\t}\n\n\tstatus := annotations[admissionWebhookAnnotationStatusKey]\n\n\t// determine whether to perform mutation based on annotation for the target resource\n\tvar required bool\n\tif strings.ToLower(status) == \"injected\" {\n\t\trequired = false\n\t} else {\n\t\tswitch strings.ToLower(annotations[admissionWebhookAnnotationInjectKey]) {\n\t\tdefault:\n\t\t\trequired = true\n\t\tcase \"n\", \"not\", \"false\", \"off\":\n\t\t\trequired = false\n\t\t}\n\t}\n\n\tinfoLogger.Printf(\"Mutation policy for %v/%v: status: %q required:%v\", metadata.Namespace, metadata.Name, status, required)\n\treturn required\n}\n\nfunc addContainer(target, added []corev1.Container, basePath string) (patch []patchOperation) {\n\tfirst := len(target) == 0\n\tvar value interface{}\n\tfor _, add := range added {\n\t\tvalue = add\n\t\tpath := basePath\n\t\tif first {\n\t\t\tfirst = false\n\t\t\tvalue = []corev1.Container{add}\n\t\t} else {\n\t\t\tpath = path + \"/-\"\n\t\t}\n\t\tpatch = append(patch, patchOperation{\n\t\t\tOp:    \"add\",\n\t\t\tPath:  path,\n\t\t\tValue: value,\n\t\t})\n\t}\n\treturn patch\n}\n\nfunc addVolume(target, added []corev1.Volume, basePath string) (patch []patchOperation) {\n\tfirst := len(target) == 0\n\tvar value interface{}\n\tfor _, add := range added {\n\t\tvalue = add\n\t\tpath := basePath\n\t\tif first {\n\t\t\tfirst = false\n\t\t\tvalue = []corev1.Volume{add}\n\t\t} else {\n\t\t\tpath = path + \"/-\"\n\t\t}\n\t\tpatch = append(patch, patchOperation{\n\t\t\tOp:    \"add\",\n\t\t\tPath:  path,\n\t\t\tValue: value,\n\t\t})\n\t}\n\treturn patch\n}\n\nfunc updateAnnotation(target map[string]string, added map[string]string) (patch []patchOperation) {\n\tfor key, value := range added {\n\t\tif target == nil || target[key] == \"\" {\n\t\t\ttarget = map[string]string{}\n\t\t\tpatch = append(patch, patchOperation{\n\t\t\t\tOp:   \"add\",\n\t\t\t\tPath: \"/metadata/annotations\",\n\t\t\t\tValue: map[string]string{\n\t\t\t\t\tkey: value,\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\tpatch = append(patch, patchOperation{\n\t\t\t\tOp:    \"replace\",\n\t\t\t\tPath:  \"/metadata/annotations/\" + key,\n\t\t\t\tValue: value,\n\t\t\t})\n\t\t}\n\t}\n\treturn patch\n}\n\n// create mutation patch for resoures\nfunc createPatch(pod *corev1.Pod, sidecarConfig *Config, annotations map[string]string) ([]byte, error) {\n\tvar patch []patchOperation\n\n\tpatch = append(patch, addContainer(pod.Spec.Containers, sidecarConfig.Containers, \"/spec/containers\")...)\n\tpatch = append(patch, addVolume(pod.Spec.Volumes, sidecarConfig.Volumes, \"/spec/volumes\")...)\n\tpatch = append(patch, updateAnnotation(pod.Annotations, annotations)...)\n\n\treturn json.Marshal(patch)\n}\n\n// main mutation process\nfunc (whsvr *WebhookServer) mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {\n\treq := ar.Request\n\tvar pod corev1.Pod\n\tif err := json.Unmarshal(req.Object.Raw, &pod); err != nil {\n\t\twarningLogger.Printf(\"Could not unmarshal raw object: %v\", err)\n\t\treturn &admissionv1.AdmissionResponse{\n\t\t\tResult: &metav1.Status{\n\t\t\t\tMessage: err.Error(),\n\t\t\t},\n\t\t}\n\t}\n\n\tinfoLogger.Printf(\"AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v\",\n\t\treq.Kind, req.Namespace, req.Name, pod.Name, req.UID, req.Operation, req.UserInfo)\n\n\t// determine whether to perform mutation\n\tif !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) {\n\t\tinfoLogger.Printf(\"Skipping mutation for %s/%s due to policy check\", pod.Namespace, pod.Name)\n\t\treturn &admissionv1.AdmissionResponse{\n\t\t\tAllowed: true,\n\t\t}\n\t}\n\n\tannotations := map[string]string{admissionWebhookAnnotationStatusKey: \"injected\"}\n\tpatchBytes, err := createPatch(&pod, whsvr.sidecarConfig, annotations)\n\tif err != nil {\n\t\treturn &admissionv1.AdmissionResponse{\n\t\t\tResult: &metav1.Status{\n\t\t\t\tMessage: err.Error(),\n\t\t\t},\n\t\t}\n\t}\n\n\tinfoLogger.Printf(\"AdmissionResponse: patch=%v\\n\", string(patchBytes))\n\treturn &admissionv1.AdmissionResponse{\n\t\tAllowed: true,\n\t\tPatch:   patchBytes,\n\t\tPatchType: func() *admissionv1.PatchType {\n\t\t\tpt := admissionv1.PatchTypeJSONPatch\n\t\t\treturn &pt\n\t\t}(),\n\t}\n}\n\n// Serve method for webhook server\nfunc (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {\n\tvar body []byte\n\tif r.Body != nil {\n\t\tif data, err := ioutil.ReadAll(r.Body); err == nil {\n\t\t\tbody = data\n\t\t}\n\t}\n\tif len(body) == 0 {\n\t\twarningLogger.Println(\"empty body\")\n\t\thttp.Error(w, \"empty body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// verify the content type is accurate\n\tcontentType := r.Header.Get(\"Content-Type\")\n\tif contentType != \"application/json\" {\n\t\twarningLogger.Printf(\"Content-Type=%s, expect application/json\", contentType)\n\t\thttp.Error(w, \"invalid Content-Type, expect `application/json`\", http.StatusUnsupportedMediaType)\n\t\treturn\n\t}\n\n\tvar admissionResponse *admissionv1.AdmissionResponse\n\tar := admissionv1.AdmissionReview{}\n\tif _, _, err := deserializer.Decode(body, nil, &ar); err != nil {\n\t\twarningLogger.Printf(\"Can't decode body: %v\", err)\n\t\tadmissionResponse = &admissionv1.AdmissionResponse{\n\t\t\tResult: &metav1.Status{\n\t\t\t\tMessage: err.Error(),\n\t\t\t},\n\t\t}\n\t} else {\n\t\tadmissionResponse = whsvr.mutate(&ar)\n\t}\n\n\tadmissionReview := admissionv1.AdmissionReview{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: \"admission.k8s.io/v1\",\n\t\t\tKind:       \"AdmissionReview\",\n\t\t},\n\t}\n\tif admissionResponse != nil {\n\t\tadmissionReview.Response = admissionResponse\n\t\tif ar.Request != nil {\n\t\t\tadmissionReview.Response.UID = ar.Request.UID\n\t\t}\n\t}\n\n\tresp, err := json.Marshal(admissionReview)\n\tif err != nil {\n\t\twarningLogger.Printf(\"Can't encode response: %v\", err)\n\t\thttp.Error(w, fmt.Sprintf(\"could not encode response: %v\", err), http.StatusInternalServerError)\n\t}\n\tinfoLogger.Printf(\"Ready to write reponse ...\")\n\tif _, err := w.Write(resp); err != nil {\n\t\twarningLogger.Printf(\"Can't write response: %v\", err)\n\t\thttp.Error(w, fmt.Sprintf(\"could not write response: %v\", err), http.StatusInternalServerError)\n\t}\n}\n"
  },
  {
    "path": "cmd/webhookconfig.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"reflect\"\n\n\tadmissionregistrationv1 \"k8s.io/api/admissionregistration/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nvar (\n\twebhookConfigName = \"sidecar-injector-webhook\"\n\twebhookInjectPath = \"/inject\"\n)\n\nfunc createOrUpdateMutatingWebhookConfiguration(caPEM *bytes.Buffer, webhookService, webhookNamespace string) error {\n\tinfoLogger.Println(\"Initializing the kube client...\")\n\n\tkubeconfig := os.Getenv(\"KUBECONFIG\")\n\tconfig, err := clientcmd.BuildConfigFromFlags(\"\", kubeconfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmutatingWebhookConfigV1Client := clientset.AdmissionregistrationV1()\n\n\tinfoLogger.Printf(\"Creating or updating the mutatingwebhookconfiguration: %s\", webhookConfigName)\n\tfail := admissionregistrationv1.Fail\n\tsideEffect := admissionregistrationv1.SideEffectClassNone\n\tmutatingWebhookConfig := &admissionregistrationv1.MutatingWebhookConfiguration{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: webhookConfigName,\n\t\t},\n\t\tWebhooks: []admissionregistrationv1.MutatingWebhook{{\n\t\t\tName:                    \"sidecar-injector.morven.me\",\n\t\t\tAdmissionReviewVersions: []string{\"v1\", \"v1beta1\"},\n\t\t\tSideEffects:             &sideEffect,\n\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\tCABundle: caPEM.Bytes(), // self-generated CA for the webhook\n\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\tName:      webhookService,\n\t\t\t\t\tNamespace: webhookNamespace,\n\t\t\t\t\tPath:      &webhookInjectPath,\n\t\t\t\t},\n\t\t\t},\n\t\t\tRules: []admissionregistrationv1.RuleWithOperations{\n\t\t\t\t{\n\t\t\t\t\tOperations: []admissionregistrationv1.OperationType{\n\t\t\t\t\t\tadmissionregistrationv1.Create,\n\t\t\t\t\t\tadmissionregistrationv1.Update,\n\t\t\t\t\t},\n\t\t\t\t\tRule: admissionregistrationv1.Rule{\n\t\t\t\t\t\tAPIGroups:   []string{\"\"},\n\t\t\t\t\t\tAPIVersions: []string{\"v1\"},\n\t\t\t\t\t\tResources:   []string{\"pods\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tNamespaceSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\"sidecar-injection\": \"enabled\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tFailurePolicy: &fail,\n\t\t}},\n\t}\n\n\tfoundWebhookConfig, err := mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Get(context.TODO(), webhookConfigName, metav1.GetOptions{})\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\tif _, err := mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Create(context.TODO(), mutatingWebhookConfig, metav1.CreateOptions{}); err != nil {\n\t\t\twarningLogger.Printf(\"Failed to create the mutatingwebhookconfiguration: %s\", webhookConfigName)\n\t\t\treturn err\n\t\t}\n\t\tinfoLogger.Printf(\"Created mutatingwebhookconfiguration: %s\", webhookConfigName)\n\t} else if err != nil {\n\t\twarningLogger.Printf(\"Failed to check the mutatingwebhookconfiguration: %s\", webhookConfigName)\n\t\treturn err\n\t} else {\n\t\t// there is an existing mutatingWebhookConfiguration\n\t\tif len(foundWebhookConfig.Webhooks) != len(mutatingWebhookConfig.Webhooks) ||\n\t\t\t!(foundWebhookConfig.Webhooks[0].Name == mutatingWebhookConfig.Webhooks[0].Name &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].AdmissionReviewVersions, mutatingWebhookConfig.Webhooks[0].AdmissionReviewVersions) &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].SideEffects, mutatingWebhookConfig.Webhooks[0].SideEffects) &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].FailurePolicy, mutatingWebhookConfig.Webhooks[0].FailurePolicy) &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].Rules, mutatingWebhookConfig.Webhooks[0].Rules) &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].NamespaceSelector, mutatingWebhookConfig.Webhooks[0].NamespaceSelector) &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].ClientConfig.CABundle, mutatingWebhookConfig.Webhooks[0].ClientConfig.CABundle) &&\n\t\t\t\treflect.DeepEqual(foundWebhookConfig.Webhooks[0].ClientConfig.Service, mutatingWebhookConfig.Webhooks[0].ClientConfig.Service)) {\n\t\t\tmutatingWebhookConfig.ObjectMeta.ResourceVersion = foundWebhookConfig.ObjectMeta.ResourceVersion\n\t\t\tif _, err := mutatingWebhookConfigV1Client.MutatingWebhookConfigurations().Update(context.TODO(), mutatingWebhookConfig, metav1.UpdateOptions{}); err != nil {\n\t\t\t\twarningLogger.Printf(\"Failed to update the mutatingwebhookconfiguration: %s\", webhookConfigName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tinfoLogger.Printf(\"Updated the mutatingwebhookconfiguration: %s\", webhookConfigName)\n\t\t}\n\t\tinfoLogger.Printf(\"The mutatingwebhookconfiguration: %s already exists and has no change\", webhookConfigName)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "deploy/clusterrole.yaml",
    "content": "kind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: sidecar-injector\n  labels:\n    app: sidecar-injector\nrules:\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\"]\n  verbs: [\"create\", \"get\", \"delete\", \"list\", \"patch\", \"update\", \"watch\"]\n"
  },
  {
    "path": "deploy/clusterrolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: sidecar-injector\n  labels:\n    app: sidecar-injector\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: sidecar-injector\nsubjects:\n- kind: ServiceAccount\n  name: sidecar-injector\n  namespace: sidecar-injector\n"
  },
  {
    "path": "deploy/configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: sidecar-injector\n  labels:\n    app: sidecar-injector\ndata:\n  sidecarconfig.yaml: |\n    containers:\n    - name: sidecar-nginx\n      image: nginx:1.12.2\n      imagePullPolicy: IfNotPresent\n      volumeMounts:\n      - name: nginx-conf\n        mountPath: /etc/nginx\n    volumes:\n    - name: nginx-conf\n      configMap:\n        name: nginx-configmap\n"
  },
  {
    "path": "deploy/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: sidecar-injector\n  labels:\n    app: sidecar-injector\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: sidecar-injector\n  template:\n    metadata:\n      labels:\n        app: sidecar-injector\n    spec:\n      serviceAccountName: sidecar-injector\n      containers:\n        - name: sidecar-injector\n          image: sidecar-injector\n          imagePullPolicy: Always\n          args:\n          - -service-name=sidecar-injector\n          - -sidecar-config-file=/etc/webhook/config/sidecarconfig.yaml\n          env:\n          - name: POD_NAMESPACE\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.namespace\n          lifecycle:\n            preStop:\n              exec:\n                command: [\"/bin/sh\", \"-c\", \"/prestop.sh\"]\n          volumeMounts:\n          - name: webhook-config\n            mountPath: /etc/webhook/config\n      volumes:\n      - name: webhook-config\n        configMap:\n          name: sidecar-injector\n"
  },
  {
    "path": "deploy/kustomization.yaml",
    "content": "namespace: sidecar-injector\n\nresources:\n- namespace.yaml\n- clusterrole.yaml\n- clusterrolebinding.yaml\n- deployment.yaml\n- service.yaml\n- serviceaccount.yaml\n- configmap.yaml\n- nginx-configmap.yaml\n\nimages:\n- name: sidecar-injector\n  newName: quay.io/morvencao/sidecar-injector\n  newTag: latest\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\n"
  },
  {
    "path": "deploy/namespace.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: sidecar-injector\n\n"
  },
  {
    "path": "deploy/nginx-configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx-configmap\ndata:\n  nginx.conf: |\n    worker_processes  1;\n    \n    events {\n      worker_connections  1024;\n    }\n    \n    http {\n      default_type  application/octet-stream;\n      \n      sendfile        on;\n      keepalive_timeout  65;\n      \n      server {\n        listen       80;\n        server_name  localhost;\n\n        location / {\n            root   html;\n            index  index.html index.htm;\n        }\n\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   html;\n        }\n      }\n    }\n"
  },
  {
    "path": "deploy/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: sidecar-injector\n  labels:\n    app: sidecar-injector\nspec:\n  ports:\n  - port: 443\n    targetPort: 8443\n  selector:\n    app: sidecar-injector\n"
  },
  {
    "path": "deploy/serviceaccount.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: sidecar-injector\n  labels:\n    app: sidecar-injector\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/morvencao/kube-sidecar-injector\n\ngo 1.17\n\nrequire (\n\tgopkg.in/yaml.v2 v2.4.0\n\tk8s.io/api v0.19.15\n\tk8s.io/apimachinery v0.19.15\n\tk8s.io/client-go v0.19.15\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/go-logr/logr v0.2.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/protobuf v1.4.2 // indirect\n\tgithub.com/google/gofuzz v1.1.0 // indirect\n\tgithub.com/googleapis/gnostic v0.4.1 // indirect\n\tgithub.com/imdario/mergo v0.3.5 // indirect\n\tgithub.com/json-iterator/go v1.1.10 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.1 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect\n\tgolang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect\n\tgolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect\n\tgolang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect\n\tgolang.org/x/text v0.3.3 // indirect\n\tgolang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect\n\tgoogle.golang.org/appengine v1.6.5 // indirect\n\tgoogle.golang.org/protobuf v1.24.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tk8s.io/klog/v2 v2.2.0 // indirect\n\tk8s.io/utils v0.0.0-20200729134348-d5654de09c73 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect\n\tsigs.k8s.io/yaml v1.2.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=\ngithub.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=\ngithub.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=\ngithub.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=\ngithub.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=\ngithub.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=\ngithub.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=\ngithub.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=\ngithub.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=\ngithub.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=\ngithub.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\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/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\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-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=\ngithub.com/go-logr/logr v0.2.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/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=\ngithub.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=\ngithub.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=\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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.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 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/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 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=\ngithub.com/google/go-cmp v0.4.0/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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=\ngithub.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=\ngithub.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/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 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=\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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\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/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/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=\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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=\ngolang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\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 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\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=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nk8s.io/api v0.19.15 h1:i22aQYrQ9gaBHEAS9XvyR5ZfrTDAd+Q+JwWM+xIBv30=\nk8s.io/api v0.19.15/go.mod h1:rMRWjnIJQmurd/FdLobht6dCSbJQ+UDpyOwPaoFS7lI=\nk8s.io/apimachinery v0.19.15 h1:P37ni6/yFxRMrqgM75k/vt5xq9vnNiR3rJPTmWXrNho=\nk8s.io/apimachinery v0.19.15/go.mod h1:RMyblyny2ZcDQ/oVE+lC31u7XTHUaSXEK2IhgtwGxfc=\nk8s.io/client-go v0.19.15 h1:lDBvFBjDIExh0mFS6JbG+5B7ghuPhqXjBzlaxG81ToU=\nk8s.io/client-go v0.19.15/go.mod h1:OJMQWgHQJRDtO2BVtpkHUQOq/e5WHpXc02lSdPI0S/k=\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 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=\nk8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=\nk8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg=\nk8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.1/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": "medium-article.md",
    "content": "# Diving into Kubernetes MutatingAdmissionWebhook\n\n[Admission controllers](https://kubernetes.io/docs/admin/admission-controllers/) are powerful tools for intercepting requests to the Kubernetes API server prior to persistence of the object. However, they are not very flexible due to the requirement that they are compiled into binary into `kube-apiserver` and configured by the cluster administrator. Starting in Kubernetes 1.7, [Initializers](https://v1-8.docs.kubernetes.io/docs/admin/extensible-admission-controllers/#initializers) and [External Admission Webhooks](https://v1-8.docs.kubernetes.io/docs/admin/extensible-admission-controllers/#external-admission-webhooks) are introduced to address this limitation. In Kubernetes 1.9, `Initializers` stays in alpha phase while `External Admission Webhooks` have been promoted to beta and split into [MutatingAdmissionWebhook](https://kubernetes.io/docs/admin/admission-controllers/#mutatingadmissionwebhook-beta-in-19) and [ValidatingAdmissionWebhook](https://kubernetes.io/docs/admin/admission-controllers/#validatingadmissionwebhook-alpha-in-18-beta-in-19).\n\n`MutatingAdmissionWebhook` together with `ValidatingAdmissionWebhook` are a special kind of `admission controllers` which process mutating and validating on requests matching the rules defined in [MutatingWebhookConfiguration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#mutatingwebhookconfiguration-v1beta1-admissionregistration)(explained below).\n\nIn this article, we'll dive into the details of `MutatingAdmissionWebhook` and write a working webhook admission server step by step.\n\n## Benefit of `Webhooks`\n\n`Webhooks` allow Kubernetes cluster-admin to create additional mutating and validating admission plugins to the admission chain of `apiserver` without recompiling them. This provides end-developer with the freedom and flexibility to customize admission logic on multiple actions(\"CREATA\", \"UPDATE\", \"DELETE\"...) on any resource. The possible applications are vast. Some common use cases includes:\n- Mutating resources before creating them. [Istio](https://github.com/istio), a representative example, injecting [Envoy](https://github.com/envoyproxy/envoy) sidecar container to target pods to implement traffic management and policy enforcement.\n- Automated provisioning of `StorageClass`. Observes creation of `PersistentVolumeClaim` objects and automatically adds storage class to them based on predefined policy. Users that do not need to care about `StorageClass` creating.\n- Validating complex custom resource. Make sure custom resource can only be created after its definition and all dependencies created and available.\n- Restricting namespace. On multi-tenant systems, avoid resources created in reserved namespaces.\n\nBesides the user-cases listed above, many more aplications can be created based on the power of `webhooks`.\n\n## `Webhooks` vs `Initializers`\n\nBased on feedback from the community and use cases in alpha phase of both `External Admission Webhooks` and `Initializers`, the Kubernetes community decided to promote webhooks to beta and split it into `MutatingAdmissionWebhook` and `ValidatingAdmissionWebhook`. These updates make webhooks consistent with other admission controllers and enforce `mutate-before-validate`. `Initializers` can also implement dynamic admission control by modifying Kubernetes resources before they are actually created. If you're unfamiliar with `Initializers`, please refer to the acrtcle: https://medium.com/ibm-cloud/kubernetes-initializers-deep-dive-and-tutorial-3bc416e4e13e.\n\nSo what's the difference between `Webhooks` and `Initializers`?\n\n- `Webhooks` can be applied on more actions, including 'mutate' or 'admit' on resoures 'CREATE' 'UPDATE' and 'DELETE', whereas `Initializers` can't 'admit' resources for 'DELETE' requests.\n- `Webhooks` are not allowed to query resources before created, while `Initializers` are capable of watching the uninitialized resources by the query parameter `?includeUninitialized=true`, which makes resources creating progress transparent.\n- Since the `Initializers` persist the 'pre-create' states to `etcd`, higher latency and increased `etcd` burden will be introduced accordingly, especially when `apiserver` upgrades or fails. `Webhooks`, however, consume less memory and computing resources.\n- More robustness on failures for `Webhooks` than `Initializers`. Failure policy can be configured in `Webhooks` configuraton to avoid hanging onto resources that are created. Buggy `Initializers`, on the other hand, may block all matched resources creating.\n\nBesides the difference listed above, `Initializer` is stuck in some open issues with long expected development time including quota replenishment bug. Promotion of `Webhooks` to beta may be a signal that more support for it in the future, but that depends. If stable behavior is preferred, suggest you choose `Webhooks`.\n\n## How MutatingAdmissionWebhook works\n\n`MutatingAdmissionWebhook` intercepts requests matching the rules defined in `MutatingWebhookConfiguration` before presisting into [etcd](https://github.com/coreos/etcd). `MutatingAdmissionWebhook` executes the mutation by sending admission requests to webhook server. Webhook server is just plain http server that adhere to the [API](https://github.com/kubernetes/kubernetes/blob/v1.9.0/pkg/apis/admission/types.go).\n\nThe following diagram describes how `MutatingAdmissionWebhook` works in details:\n\n![](https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/mutating-admission-webhook.jpg)\n\nThe `MutatingAdmissionWebhook` needs three objects to function:\n\n1. **MutatingWebhookConfiguration**\n   \n   `MutatingAdmissionWebhook` need to be registered in the `apiserver` by providing `MutatingWebhookConfiguration`. During the registration process, MutatingAdmissionWebhook states:\n   - How to connect to the webhook admission server\n   - How to verify the webhook admission server\n   - The URL path of the webhook admission server\n   - Rules defining which resource and what action it handles\n   - How unrecognized errors from the webhook admission server are handled\n\n2. **MutatingAdmissionWebhook itself**\n\n   `MutatingAdmissionWebhook` is a plugin-style admission controller that can be configured into the `apiserver`. The `MutatingAdmissionWebhook` plugin get the list of interested admission webhooks from `MutatingWebhookConfiguration`. Then the `MutatingAdmissionWebhook` observes the requests to apiserver and intercepts requests matching the rules in admission webhooks and calls them in parallel.\n\n3. **Webhook Admission Server**\n   \n   `Webhook Admission Server` is just plain http server that adhere to Kubernetes [API](https://github.com/kubernetes/kubernetes/blob/v1.9.0/pkg/apis/admission/types.go). \n   For each request to the `apiserver`, the `MutatingAdmissionWebhook` sends an `admissionReview`([API](https://github.com/kubernetes/kubernetes/blob/v1.9.0/pkg/apis/admission/types.go) for reference) to the relevant webhook admission server. The webhook admission server gathers information like `object`, `oldobject`, and `userInfo` from `admissionReview`, and sends back a `admissionReview` response including `AdmissionResponse` whose `Allowed` and `Result` fields are filled with the admission decision and optional `Patch` to mutate the resoures.\n   \n## Tutorial for MutatingAdmissionWebhook\n\nWrite a complete Webhook Admission Server may be intimidating. To make it easier, we'll write a simple Webhook Admission Server that implements injecting nginx sidecar container and volume. The complete code can be found in [kube-mutating-webhook-tutorial](https://github.com/morvencao/kube-mutating-webhook-tutorial). The project refers to [Kunernetes webhook example](https://github.com/kubernetes/kubernetes/tree/release-1.9/test/images/webhook) and [Istio sidecar injection implementation](https://github.com/istio/istio/tree/master/pilot/pkg/kube/inject).\n\nIn the following sections, I'll show you how to write a working containerized webhook admission server and deploy it to a Kubernetes cluster.\n\n#### Prerequisites\n\n`MutatingAdmissionWebhook` requires a Kubernetes 1.9.0 or above with the `admissionregistration.k8s.io/v1beta1` API enabled. Verify that by the following command:\n```\nkubectl api-versions | grep admissionregistration.k8s.io/v1beta1\n```\nThe result should be:\n```\nadmissionregistration.k8s.io/v1beta1\n```\nIn addition, the `MutatingAdmissionWebhook` and `ValidatingAdmissionWebhook` admission controllers should be added and listed in the correct order in the `admission-control` flag of `kube-apiserver`.\n\n### Write the Webhook Server\n\n`Webhook Admission Server` is just plain http server that adhere to Kubernetes [API](https://github.com/kubernetes/kubernetes/blob/v1.9.0/pkg/apis/admission/types.go). \nI'll paste some pseudo code to describe the main logic:\n```\nsidecarConfig, err := loadConfig(parameters.sidecarCfgFile)\npair, err := tls.LoadX509KeyPair(parameters.certFile, parameters.keyFile)\n\nwhsvr := &WebhookServer {\n    sidecarConfig:    sidecarConfig,\n    server:           &http.Server {\n        Addr:        fmt.Sprintf(\":%v\", 443),\n        TLSConfig:   &tls.Config{Certificates: []tls.Certificate{pair}},\n    },\n}\n\t\n// define http server and server handler\nmux := http.NewServeMux()\nmux.HandleFunc(\"/mutate\", whsvr.serve)\nwhsvr.server.Handler = mux\n\n// start webhook server in new rountine\ngo func() {\n    if err := whsvr.server.ListenAndServeTLS(\"\", \"\"); err != nil {\n        glog.Errorf(\"Filed to listen and serve webhook server: %v\", err)\n    }\n}()\n```\nExplanation for the above code:\n\n- `sidecarCfgFile` contains sidecar injector template defined in `ConfigMap` below.\n- `certFile` and `keyFile` key pair that will be needed for TLS communication between `apiserver` and `webhook server`.\n- Line 19 starts https server listening on 443 on path '/mutate'. \n\nNext we'll focus on the main logic of handler function `serve`:\n```\n// Serve method for webhook server\nfunc (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {\n\tvar body []byte\n\tif r.Body != nil {\n\t\tif data, err := ioutil.ReadAll(r.Body); err == nil {\n\t\t\tbody = data\n\t\t}\n\t}\n\n\tvar reviewResponse *v1beta1.AdmissionResponse\n\tar := v1beta1.AdmissionReview{}\n\tdeserializer := codecs.UniversalDeserializer()\n\tif _, _, err := deserializer.Decode(body, nil, &ar); err != nil {\n\t\tglog.Error(err)\n\t\treviewResponse = toAdmissionResponse(err)\n\t} else {\n\t\treviewResponse = mutate(ar)\n\t}\n\n\tresponse := v1beta1.AdmissionReview{}\n\tif reviewResponse != nil {\n\t\tresponse.Response = reviewResponse\n\t\tresponse.Response.UID = ar.Request.UID\n\t}\n\t// reset the Object and OldObject, they are not needed in a response.\n\tar.Request.Object = runtime.RawExtension{}\n\tar.Request.OldObject = runtime.RawExtension{}\n\n\tresp, err := json.Marshal(response)\n\tif err != nil {\n\t\tglog.Error(err)\n\t}\n\tif _, err := w.Write(resp); err != nil {\n\t\tglog.Error(err)\n\t}\n}\n```\nThe `serve` function is plain http handler with `http request` and `response writer` parameters. \n- Firstly unmarshals the request to `AdmissionReview`, which contains information like `object`, `oldobject` and `userInfo`...\n- Then calls Webhook core function `mutate` to create `patch` that injects sidecar container and volume. \n- Finally, unmarshals the response with admission decision and optional patch, sends it back to `apiserver`.\n\nFor the part of `mutate` function, you get the free rein to complete it in your preferred way. Let's take my implementation as an example:\n```\n// main mutation process\nfunc (whsvr *WebhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {\n\treq := ar.Request\n\tvar pod corev1.Pod\n\tif err := json.Unmarshal(req.Object.Raw, &pod); err != nil {\n\t\tglog.Errorf(\"Could not unmarshal raw object: %v\", err)\n\t\treturn &v1beta1.AdmissionResponse {\n\t\t\tResult: &metav1.Status {\n\t\t\t\tMessage: err.Error(),\n\t\t\t},\n\t\t}\n\t}\n\t\n\t// determine whether to perform mutation\n\tif !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) {\n\t\tglog.Infof(\"Skipping mutation for %s/%s due to policy check\", pod.Namespace, pod.Name)\n\t\treturn &v1beta1.AdmissionResponse {\n\t\t\tAllowed: true, \n\t\t}\n\t}\n\n\tannotations := map[string]string{admissionWebhookAnnotationStatusKey: \"injected\"}\n\tpatchBytes, err := createPatch(&pod, whsvr.sidecarConfig, annotations)\n\t\n\treturn &v1beta1.AdmissionResponse {\n\t\tAllowed: true,\n\t\tPatch:   patchBytes,\n\t\tPatchType: func() *v1beta1.PatchType {\n\t\t\tpt := v1beta1.PatchTypeJSONPatch\n\t\t\treturn &pt\n\t\t}(),\n\t}\n}\n```\nFrom the code above, the `mutate` function calls [mutationRequired](https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/webhook.go#L98-L130) to detemine whether mutation is required or not. For those requiring mutation, the `mutate` function gets mutation 'patch' from another function [createPatch](https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/webhook.go#L196-L205). Pay attention to the little trick in function `mutationRequired`, we skip the `pods` without annotation `sidecar-injector-webhook.morven.me/inject: true`. That will be mentioned latter when we deployment applications. For complete code, please refer to https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/webhook.go.\n\n#### Create Dockerfile and Build the Container\n\nCreate the `build` script:\n```\ndep ensure\nCGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o kube-mutating-webhook-tutorial .\ndocker build --no-cache -t morvencao/sidecar-injector:v1 .\nrm -rf kube-mutating-webhook-tutorial\n\ndocker push morvencao/sidecar-injector:v1\n```\n\nAnd create `Dockerfile` as dependency of build script:\n```\nFROM alpine:latest\n\nADD kube-mutating-webhook-tutorial /kube-mutating-webhook-tutorial\nENTRYPOINT [\"./kube-mutating-webhook-tutorial\"]\n```\n\nBefore actually building the container, you need a [Docker](https://hub.docker.com/) ID account and change the image name&tag(in `Dockerfile` and `deployment.yaml`) to yours, then execute:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# ./build\nSending build context to Docker daemon  44.89MB\nStep 1/3 : FROM alpine:latest\n ---> 3fd9065eaf02\nStep 2/3 : ADD kube-mutating-webhook-tutorial /kube-mutating-webhook-tutorial\n ---> 432de60c2b3f\nStep 3/3 : ENTRYPOINT [\"./kube-mutating-webhook-tutorial\"]\n ---> Running in da6e956d1755\nRemoving intermediate container da6e956d1755\n ---> 619faa936145\nSuccessfully built 619faa936145\nSuccessfully tagged morvencao/sidecar-injector:v1\nThe push refers to repository [docker.io/morvencao/sidecar-injector]\nefd05fe119bb: Pushed\ncd7100a72410: Layer already exists\nv1: digest: sha256:7a4889928ec5a8bcfb91b610dab812e5228d8dfbd2b540cd7a341c11f24729bf size: 739\n```\n\n#### Create Sidecar Injection Configuration\n\nNow let's create a Kubernetes `ConfigMap`, which includes `container` and `volume` information that will be injected into the target pod.\n```\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: sidecar-injector-webhook-configmap\ndata:\n  sidecarconfig.yaml: |\n    containers:\n      - name: sidecar-nginx\n        image: nginx:1.12.2\n        imagePullPolicy: IfNotPresent\n        ports:\n          - containerPort: 80\n        volumeMounts:\n          - name: nginx-conf\n            mountPath: /etc/nginx\n    volumes:\n      - name: nginx-conf\n        configMap:\n          name: nginx-configmap\n```\nFrom the above manifest, another ConfigMap including `nginx conf` is required. Here we put it in [nginxconfigmap.yaml](https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/deploy/nginxconfigmap.yaml).\n\nThen deploy the two `ConfigMap`s to cluster:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deploy/nginxconfigmap.yaml\nconfigmap \"nginx-configmap\" created\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deploy/configmap.yaml\nconfigmap \"sidecar-injector-webhook-configmap\" created\n```\n\n#### Create Secret Including Signed key/cert Pair\n\nSupporting `TLS` for external webhook server is required, because admission is a high security operation. so we need to create TLS certificate signed by `Kubernetes CA` for to secure the communcation between webhook server and `apiserver`. For the complete creating and approving `CSR` process, please refer to https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/. \n\nFor simplicity purposes, we refer to the [script](https://github.com/istio/istio/blob/master/install/kubernetes/webhook-create-signed-cert.sh) from `Istio` and write a similar script called `webhook-create-signed-cert.sh` to automatically create the cert/key pair and include it in a Kubernetes `secret`.\n```\n#!/bin/bash\nwhile [[ $# -gt 0 ]]; do\n    case ${1} in\n        --service)\n            service=\"$2\"\n            shift\n            ;;\n        --secret)\n            secret=\"$2\"\n            shift\n            ;;\n        --namespace)\n            namespace=\"$2\"\n            shift\n            ;;\n    esac\n    shift\ndone\n\n[ -z ${service} ] && service=sidecar-injector-webhook-svc\n[ -z ${secret} ] && secret=sidecar-injector-webhook-certs\n[ -z ${namespace} ] && namespace=default\n\ncsrName=${service}.${namespace}\ntmpdir=$(mktemp -d)\necho \"creating certs in tmpdir ${tmpdir} \"\n\ncat <<EOF >> ${tmpdir}/csr.conf\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = serverAuth\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = ${service}\nDNS.2 = ${service}.${namespace}\nDNS.3 = ${service}.${namespace}.svc\nEOF\n\nopenssl genrsa -out ${tmpdir}/server-key.pem 2048\nopenssl req -new -key ${tmpdir}/server-key.pem -subj \"/CN=${service}.${namespace}.svc\" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf\n\n# clean-up any previously created CSR for our service. Ignore errors if not present.\nkubectl delete csr ${csrName} 2>/dev/null || true\n\n# create  server cert/key CSR and  send to k8s API\ncat <<EOF | kubectl create -f -\napiVersion: certificates.k8s.io/v1beta1\nkind: CertificateSigningRequest\nmetadata:\n  name: ${csrName}\nspec:\n  groups:\n  - system:authenticated\n  request: $(cat ${tmpdir}/server.csr | base64 | tr -d '\\n')\n  usages:\n  - digital signature\n  - key encipherment\n  - server auth\nEOF\n\n# verify CSR has been created\nwhile true; do\n    kubectl get csr ${csrName}\n    if [ \"$?\" -eq 0 ]; then\n        break\n    fi\ndone\n\n# approve and fetch the signed certificate\nkubectl certificate approve ${csrName}\n# verify certificate has been signed\nfor x in $(seq 10); do\n    serverCert=$(kubectl get csr ${csrName} -o jsonpath='{.status.certificate}')\n    if [[ ${serverCert} != '' ]]; then\n        break\n    fi\n    sleep 1\ndone\nif [[ ${serverCert} == '' ]]; then\n    echo \"ERROR: After approving csr ${csrName}, the signed certificate did not appear on the resource. Giving up after 10 attempts.\" >&2\n    exit 1\nfi\necho ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem\n\n\n# create the secret with CA cert and server cert/key\nkubectl create secret generic ${secret} \\\n        --from-file=key.pem=${tmpdir}/server-key.pem \\\n        --from-file=cert.pem=${tmpdir}/server-cert.pem \\\n        --dry-run -o yaml |\n    kubectl -n ${namespace} apply -f -\n```\n\nThen execute it and a Kubernetes `secret` including cert/key pair is created:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# ./deploy/webhook-create-signed-cert.sh\ncreating certs in tmpdir /tmp/tmp.wXZywp0wAF\nGenerating RSA private key, 2048 bit long modulus\n...........................................+++\n..........+++\ne is 65537 (0x10001)\ncertificatesigningrequest \"sidecar-injector-webhook-svc.default\" created\nNAME                                   AGE       REQUESTOR                                           CONDITION\nsidecar-injector-webhook-svc.default   0s        https://mycluster.icp:9443/oidc/endpoint/OP#admin   Pending\ncertificatesigningrequest \"sidecar-injector-webhook-svc.default\" approved\nsecret \"sidecar-injector-webhook-certs\" created\n```\n\n#### Create the Sidecar Injector Deployment and Service\n\nThe `deployment` brings up 1 `pod` in which the `sidecar-injector` container is running.  The container starts with special arguments:\n- `sidecarCfgFile` pointing to the sidecar injector configuration file mounted from `sidecar-injector-webhook-configmap` ConfigMap created above\n- `tlsCertFile` and `tlsKeyFile` are cert/key pair mounted from `sidecar-injector-webhook-certs` Secret create by script above\n- `alsologtostderr` `v=4` and `2>&1` are logging arguments\n```\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: sidecar-injector-webhook-deployment\n  labels:\n    app: sidecar-injector\nspec:\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: sidecar-injector\n    spec:\n      containers:\n        - name: sidecar-injector\n          image: morvencao/sidecar-injector:v1\n          imagePullPolicy: IfNotPresent\n          args:\n            - -sidecarCfgFile=/etc/webhook/config/sidecarconfig.yaml\n            - -tlsCertFile=/etc/webhook/certs/cert.pem\n            - -tlsKeyFile=/etc/webhook/certs/key.pem\n            - -alsologtostderr\n            - -v=4\n            - 2>&1\n          volumeMounts:\n            - name: webhook-certs\n              mountPath: /etc/webhook/certs\n              readOnly: true\n            - name: webhook-config\n              mountPath: /etc/webhook/config\n      volumes:\n        - name: webhook-certs\n          secret:\n            secretName: sidecar-injector-webhook-certs\n        - name: webhook-config\n          configMap:\n            name: sidecar-injector-webhook-configmap\n```\n\nThe `service` exposes the `pod` defined above labeled by `app=sidecar-injector` to make it accessible in cluster. This `service` will be referred by the `MutatingWebhookConfiguration` in `clientConfig` section and by default `spec.ports.port` should be **443**(default https port).\n```\napiVersion: v1\nkind: Service\nmetadata:\n  name: sidecar-injector-webhook-svc\n  labels:\n    app: sidecar-injector\nspec:\n  ports:\n  - port: 443\n    targetPort: 443\n  selector:\n    app: sidecar-injector\n```\n\nNext we deploy the above `Deployment` and `Service` to cluster and verify the `sidecar injector` webhook server is running:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deploy/deployment.yaml\ndeployment \"sidecar-injector-webhook-deployment\" created\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deploy/service.yaml\nservice \"sidecar-injector-webhook-svc\" created\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get deployment\nNAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE\nsidecar-injector-webhook-deployment   1         1         1            1           2m\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod\nNAME                                                  READY     STATUS    RESTARTS   AGE\nsidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running   0          3m\n```\n\n#### Configure webhook admission controller on the fly\n\n`MutatingWebhookConfiguration` specifies which webhook admission servers are enabled and which resources are subject to the admission server. It is recommended that you firstly deploy the webhook admission server and make sure it is working properly before creating the `MutatingWebhookConfiguration`. Otherwise, requests will be unconditionally accepted or rejected based on `failurePolicy`.\n\nFor now, we create the `MutatingWebhookConfiguration` manifest with the following content:\n```\napiVersion: admissionregistration.k8s.io/v1beta1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: sidecar-injector-webhook-cfg\n  labels:\n    app: sidecar-injector\nwebhooks:\n  - name: sidecar-injector.morven.me\n    clientConfig:\n      service:\n        name: sidecar-injector-webhook-svc\n        namespace: default\n        path: \"/mutate\"\n      caBundle: ${CA_BUNDLE}\n    rules:\n      - operations: [ \"CREATE\" ]\n        apiGroups: [\"\"]\n        apiVersions: [\"v1\"]\n        resources: [\"pods\"]\n    namespaceSelector:\n      matchLabels:\n        sidecar-injector: enabled\n```\n\nLine 8: `name` - name for the webhook, should be fully qualified. Mutiple mutating webhooks are sorted by providing order.\nLine 9: `clientConfig` - describes how to connect to the webhook admission server and the TLS certificate. In our case, we specify the `sidecar injector` service.\nLine 15: `rules` - specifies what resources and what actions the webhook server handles. In our case, only intercepts request for creating of pods.\nLine 20: `namespaceSelector` - `namespaceSelector` decides whether to send admission request the webhook server on an object based on whether the namespace for that object matches the selector.\n\nBefore deploying the `MutatingWebhookConfiguration`, we need to replace the `${CA_BUNDLE}` with apiserver's default `caBundle`. Let's write the script `webhook-patch-ca-bundle.sh` to automate this process:\n```\n#!/bin/bash\nset -o errexit\nset -o nounset\nset -o pipefail\n\nROOT=$(cd $(dirname $0)/../../; pwd)\n\nexport CA_BUNDLE=$(kubectl get configmap -n kube-system extension-apiserver-authentication -o=jsonpath='{.data.client-ca-file}' | base64 | tr -d '\\n')\n\nif command -v envsubst >/dev/null 2>&1; then\n    envsubst\nelse\n    sed -e \"s|\\${CA_BUNDLE}|${CA_BUNDLE}|g\"\nfi\n```\n\nThen execute:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# cat ./deploy/mutatingwebhook.yaml |\\\n>   ./deploy/webhook-patch-ca-bundle.sh >\\\n>   ./deploy/mutatingwebhook-ca-bundle.yaml\n```\n\nFinally we can deploy `MutatingWebhookConfiguration`:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl create -f ./deploy/mutatingwebhook-ca-bundle.yaml\nmutatingwebhookconfiguration \"sidecar-injector-webhook-cfg\" created\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get mutatingwebhookconfiguration\nNAME                           AGE\nsidecar-injector-webhook-cfg   11s\n```\n\n#### Verification and Troubleshooting\n\nNow it's time to verify sidecar injector works as expected and try to see how to troubleshoot if you encounter issues.\nTypically we create and deploy a sleep application in `default` namespace to see if the sidecar can be injected.\n```\n[root@mstnode kube-mutating-webhook-tutorial]# cat <<EOF | kubectl create -f -\n> apiVersion: extensions/v1beta1\n> kind: Deployment\n> metadata:\n>   name: sleep\n> spec:\n>   replicas: 1\n>   template:\n>     metadata:\n>       annotations:\n>         sidecar-injector-webhook.morven.me/inject: \"true\"\n>       labels:\n>         app: sleep\n>     spec:\n>       containers:\n>       - name: sleep\n>         image: tutum/curl\n>         command: [\"/bin/sleep\",\"infinity\"]\n>         imagePullPolicy: IfNotPresent\n> EOF\ndeployment \"sleep\" created\n```\n\nPay close attention to the `spec.template.metadata.annotations` as there is a new annotation added:\n```\nsidecar-injector-webhook.morven.me/inject: \"true\"\n```\nThe sidecar injector has some logic to check the existence of the above annotation before injecting sidecar container and volume. \nYou're free to delete the logic or customize it before build the sidecar injector container.\n\nCheck the `deployment` and `pod`:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get deployment\nNAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE\nsidecar-injector-webhook-deployment   1         1         1            1           18m\nsleep                                 1         1         1            1           58s\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod\nNAME                                                  READY     STATUS    RESTARTS   AGE\nsidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running   0          18m\nsleep-6d79d8dc54-r66vz                                1/1       Running   0          1m\n```\nIt's not there. What's going on?\nLet's check the sidecar injector logs:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl logs -f sidecar-injector-webhook-deployment-bbb689d69-fdbgj\nI0314 08:48:15.140858       1 webhook.go:88] New configuration: sha256sum 21669464280f76170b88241fd79ecbca3dcebaec5c152a4a9a3e921ff742157f\n\n```\nWe can't find any logs that indicate webhook server got admission request, seems that request hadn't been sent to `sidecar injector` webhook server. \nSo there is a possibility that the issue is caused by configuration in `MutatingWebhookConfiguration`. Do a double check of `MutatingWebhookConfiguration` and we find following content:\n```\n    namespaceSelector:\n      matchLabels:\n        sidecar-injector: enabled\n```\n\n#### Control sidecar injector with `namespaceSelector`\n\nWe have configured 'namespaceSelector' in `MutatingWebhookConfiguration`, which means only resources in namespace matching the selector will be sent to webhook server. So we need label the `default` namespace with `sidecar-injector=enabled`:\n\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl label namespace default sidecar-injector=enabled\nnamespace \"default\" labeled\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get namespace -L sidecar-injector\nNAME          STATUS    AGE       sidecar-injector\ndefault       Active    1d        enabled\nkube-public   Active    1d\nkube-system   Active    1d\n```\n\nWe have configured the `MutatingWebhookConfiguration` resulting in the sidecar injection occuring at pod creation time. Kill the running pod and verify a new pod is created with the injected sidecar.\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl delete pod sleep-6d79d8dc54-r66vz\npod \"sleep-6d79d8dc54-r66vz\" deleted\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pods\nNAME                                                  READY     STATUS              RESTARTS   AGE\nsidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running             0          29m\nsleep-6d79d8dc54-b8ztx                                0/2       ContainerCreating   0          3s\nsleep-6d79d8dc54-r66vz                                1/1       Terminating         0          11m\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod sleep-6d79d8dc54-b8ztx -o yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    kubernetes.io/psp: default\n    sidecar-injector-webhook.morven.me/inject: \"true\"\n    sidecar-injector-webhook.morven.me/status: injected\n  labels:\n    app: sleep\n    pod-template-hash: \"2835848710\"\n  name: sleep-6d79d8dc54-b8ztx\n  namespace: default\nspec:\n  containers:\n  - command:\n    - /bin/sleep\n    - infinity\n    image: tutum/curl\n    imagePullPolicy: IfNotPresent\n    name: sleep\n    resources: {}\n    volumeMounts:\n    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n      name: default-token-d7t2r\n      readOnly: true\n  - image: nginx:1.12.2\n    imagePullPolicy: IfNotPresent\n    name: sidecar-nginx\n    ports:\n    - containerPort: 80\n      protocol: TCP\n    resources: {}\n    terminationMessagePath: /dev/termination-log\n    terminationMessagePolicy: File\n    volumeMounts:\n    - mountPath: /etc/nginx\n      name: nginx-conf\n  volumes:\n  - name: default-token-d7t2r\n    secret:\n      defaultMode: 420\n      secretName: default-token-d7t2r\n  - configMap:\n      defaultMode: 420\n      name: nginx-configmap\n    name: nginx-conf\n...\n```\nWe can see that sidecar container and volume have been injected into sleep application successfully. Until now, we have working sidecar injector with `MutatingAdmissionWebhook`. With `namespaceSelector` we can easily control whether the pods in specified namespace will be injected or not. \n\nBut there is a problem for this, with the above configurations, all of the pods in `default` namespace will be injected with a sidecar, this may be not expected for some cases.\n\n#### Control sidecar injector with `annotation`\n\nThanks to flexibility of `MutatingAdmissionWebhook`, we can easily customized the mutating logic to filter resources with specified annotations. Remember the annotation `sidecar-injector-webhook.morven.me/inject: \"true\"` mentioned above? It can be used as an extra control on sidecar injector. I have written [some code](https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/webhook.go#L98-L130) in webhook server to skip injecting for pod without the annotation.\n\nLet's give it a try. In this case, we create another sleep application without `sidecar-injector-webhook.morven.me/inject: \"true\"` annotation in `podTemplateSpec`:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl delete deployment sleep\ndeployment \"sleep\" deleted\n[root@mstnode kube-mutating-webhook-tutorial]# cat <<EOF | kubectl create -f -\napiVersion: extensions/v1beta1\n> kind: Deployment\n> metadata:\n>   name: sleep\n> spec:\n>   replicas: 1\n>   template:\n>     metadata:\n>       labels:\n>         app: sleep\n>     spec:\n>       containers:\n>       - name: sleep\n>         image: tutum/curl\n>         command: [\"/bin/sleep\",\"infinity\"]\n>         imagePullPolicy: IfNotPresent\n> EOF\ndeployment \"sleep\" created\n```\n\nAnd then verify the sidecar injector skipped the pod:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get deployment\nNAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE\nsidecar-injector-webhook-deployment   1         1         1            1           45m\nsleep                                 1         1         1            1           17s\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pod\nNAME                                                  READY     STATUS        RESTARTS   AGE\nsidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running       0          45m\nsleep-776b7bcdcd-4bz58                                1/1       Running       0          21s\n```\n\nThe output shows that the sleep application contains only one container, no extra container and volume injected.\nThen we patch the sleep deployment to add the additional annotation and verify it will be injected after recreated:\n```\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl patch deployment sleep -p '{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"sidecar-injector-webhook.morven.me/inject\": \"true\"}}}}}'\ndeployment \"sleep\" patched\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl delete pod sleep-776b7bcdcd-4bz58\npod \"sleep-776b7bcdcd-4bz58\" deleted\n[root@mstnode kube-mutating-webhook-tutorial]# kubectl get pods\nNAME                                                  READY     STATUS              RESTARTS   AGE\nsidecar-injector-webhook-deployment-bbb689d69-fdbgj   1/1       Running             0          49m\nsleep-3e42ff9e6c-6f87b                                0/2       ContainerCreating   0          18s\nsleep-776b7bcdcd-4bz58                                1/1       Terminating         0          3m\n```\nAs expected, the pod has been injected with extra sidecar container.\nNow, we got working sidecar injector with `mutatingAdmissionWebhook` and its coarse-grained control by `namespaceSelector` and fine-grained control by additional `annotation`.\n\n## Conclusion\n\n`MutatingAdmissionWebhook` is one of easiest ways of extending Kubernetes with new policy controls, resources mutation...\n\nThis feature will enable more workloads and support more ecosystem components, including [Istio](https://github.com/istio/istio) service mesh platform. Starting with Istio 0.5.0, Istio has refactored to support their auto injection code with `MutatingAdmissionWebhook` replacing `initializers`.\n\n## Reference\n\n- http://blog.kubernetes.io/2018/01/extensible-admission-is-beta.html\n- https://docs.google.com/document/d/1c4kdkY3ha9rm0OIRbGleCeaHknZ-NR1nNtDp-i8eH8E/view\n- https://v1-8.docs.kubernetes.io/docs/admin/extensible-admission-controllers/\n- https://github.com/kubernetes/kubernetes/tree/release-1.9/test/images/webhook"
  },
  {
    "path": "mutating-admission-webhook.xml",
    "content": "<mxfile userAgent=\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\" version=\"8.4.4\" editor=\"www.draw.io\" type=\"device\"><diagram name=\"Page-1\" id=\"97a40813-6b03-0c74-37c1-0168de264b11\">7Vzbkps4EP0aV+0+2CUB4vI4M8lsHpKt2clWsvuIQbbZwUAAzyVfvwIkGwnJ2OZiT8qkamI3Qojuo9PdauGJfrd+/SN1k9WX2MfhRAP+60T/MNE0CKFN/iskb5XEhE4lWKaBX4nATvA1+InplUy6CXycUVklyuM4zIOEF3pxFGEv52RumsYvfLNFHPqcIHGXuCH46rlhU/o98PMVey4Adic+4WC5ore2ET0xd72nZRpvInq/iaYvyqM6vXZZX7R9tnL9+KUm0j9O9Ls0jvPq0/r1DoeFbnm13SvObsed4ig/5AJUXfDshhv66E+bOZ66SZDh9BmndJT5G9NMjl9Jx7erfB0SASQfszyNn/BdHMYpkURxRFreLoIwFERuGCwj8tUjQyMd67ek+zwgOr+hJ9aB7xe3uX1ZBTn+mrhecc8XgjAiK3WKi1GD4p5MbcWX0J3j8HareOG29AHJvfCrUklwq3oCaRyvcZ6+kSb0Aocai4JZZyh92UGDNVnVQKFTmUvBuNx2vDMI+UBtIreP1bBPwyK8ZmS6qxurrjjecgSqoDy2Zxj0NblBB9e7BWeoVfMatJqqN4wedO9IdG+GBfr94Jl8XOblY1Yij3CgKFvE5DnrtjJ/bGJ2YpqVrHdDGjjJ6+4c6+RPd42zwo6fgwX23rwQs27JuKue+bsRsWwQRMwNV4keOBB6oBw9xSPcu+sgLGz7CYfPuCCDMWAF2SUMVUYTVVB3JBMadQcVhEpUJWnDeL2j6nOwDvJHN1oW1H4inLhxXuHUgJMzJpy0Y+A0BsK+krgh8PCN5xF15/2A7Aq9w6BnjMpk+qVB7wGnWZDlRHnf4nCzxp8L/V8ROCoCHTQiAo2OCBwDlF82uZsH0fLGXwdZFsTRdzxfxfHTkLi8ovUwtKJRXXUz1z4rWm0ZWr+RLN2/4vVC8WqO6t/Ny8KrlF0fcRZvUg//RcTuFaIXANFRKdVWQrS7z8bTh9gvHoTxH2BLogqUjb4ud3FgMAC/UmoYehMMaKClUk29ssLAwOoMzN5Qafoou1q+i+URkNDAYJaXLYIINsGRf1MUhsg3L3RJYOPxZlCqTm2SQ5SK/UahqVWlNZXJNMZkKQ5JoPbMdy9TI73DQxyUs4CZ2BGWrSyBkCvHSq+ql5DaOnIEk+ZuusR5o6PSqtvHPszQshD6aug2QwukrJ1saJHdhzO0zLEPb2hisPTtn4KLZ8VCBhX8SwRTMAMkIKaSB5wG5GmKKubBFch2gGigUiALmi4KNH2xw3aBaADQqAt281OTDmjK4sNZebSnG3NliHCBZXTlfFmEQfKJjnIVp8FP0tANj4B+twTDEiILSQ6sSyZCHxVg1u/YNPQa5BULIQDp94KEIOEgRL+LFFSjLo64+qSnas5PuLVXjrLQmSmLsHbt4GBj9uX1zOG8ni7LYEaFWx1s5wAVkoDKPCeoNABmNrKgoVV/LZsHg45mu5Pk72kYK+6i28BBWvXX4G+ChkOcLHM6NWe+e/xwTZq7JM2mLimeDZU06+ryrWR7U5a40ckxFNhXF6Plhbs4WgTLTUpkcdQeWcnG07LTiWAmwaX9clV0VEMYv5Q7Ct7oVCvTDDGoy+NkDETqJo/IbfTObbiTIBKafUBSVs9V+795GHtPf6+CaF8gXTePUvNbD1jzf5XLK3bgEZBOqk2Q/23WCd2sXPlD/z4IWSS8c5acqyy7GSsC08+bNArosSwBFIf6QxGGjWBe4QEJMty3WrOkaJCpB2wI/AvsvcMSmmsWt7eafKjuf7IzVheIu9DvvgpHwlc4THdd0Ew0zxKeVw9i4SNTXDIzzfIQaLLYMo+Kf2dJdkdw+vye5u2rAHWnr0kmqdYHw8r2kw8LMb6SckXYGRCGmk58OISpS7JDIUzIO64QGx9i+pgkZshW5bqu82qqdd7rKu8ogDIEzrLHW+U11BsHrnWD94soxEfrCI2IKNmyWutCbj+p7J4sdJevwoZJWQoMZwAYk3oarNmsBjFU2bO1rmDLLT3SErCQjJrwxKzWbsmOe0pqdV3MasHeYYntDcRlwZ3TWkO90Hgom1oFmYISU7U3rKdeBbiiRbqc/0ameNFKIyMCwuffm1wsi0pdtuf5ET8H+GVPLFm/KsU/NjhTv+rUSuEpJo/qzssGxVNSC5PW6HaCPhxF3vvnpJI2GvysZGD6yj0d7mQb0dXndcV/SmoGM9NhOQkF3VTrZa5OdSC8MoxMvo94schw17KJcdxCZa/8znh6Ii5VCnzO/MCW0He+AJrKnSy8ixhkX4txTjJHhjOzawdfJDaQJTt7dDnPBDNLqx08Ih1rptcOYxAvgMy9rN7eXm/xGrBbe+SY/XoZ2dsK7yfk0vjpbJ054jrvjsVmxCXMkUMnoiGC1LSHCbmQPERUTgYxN6EbH3qbDOpl3mvI9YuGXIptMsOHXJDfEsPC/X4DrpP2A/cbcBGW5oMuw9ofdfFbpmYGMBsOQbmTbwziv8DoDO2Nzsx+ojNrX3RmglGiM3FTK/VwyuhMaG84LQ6mY3sTcOPp7pDUW7N/FYeUJXGUqX+j6d15JNjZIylW7Qb3SNvCE+sWWsIs7sUnIfV2jaMwLYKyAUDw+B7ARZzpfXnsQ9iQgJqCmQ41UzB9TzHOEPj5lTJWk3jj86asumLNcaSUlQQVwq5tqM0E2rm4tFXcf9wShTTao343wKFr2np+Hh83SEBnS1s1R6gUTIUXhPoh+QtIXCdHVAp03iHYsOkSRstP2cspF5SfmmBWf9tH+DUpTefeFeqQn9bvYYh+xZCNYeAEVaD69vZ2S8IpvlV3bHv6g+J9uR624tzB9dgDuJ53mgu0+ZBuLuNdpwHk6+7X3avmu5/Q1z/+Dw==</diagram></mxfile>"
  },
  {
    "path": "prestop.sh",
    "content": "set -exo pipefail\n\n# Point to the internal API server hostname\nAPISERVER=https://kubernetes.default.svc\n\n# Path to ServiceAccount token\nSERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount\n\n# Read this Pod's namespace\nNAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)\n\n# Read the ServiceAccount bearer token\nTOKEN=$(cat ${SERVICEACCOUNT}/token)\n\n# Reference the internal certificate authority (CA)\nCACERT=${SERVICEACCOUNT}/ca.crt\n\nMutatingWebhookConfigurationName=sidecar-injector-webhook\n\n# Delete the validatingwebhookconfiguration with TOKEN\ncurl --cacert ${CACERT} --header \"Authorization: Bearer ${TOKEN}\" -X DELETE ${APISERVER}/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/${MutatingWebhookConfigurationName}\n\n"
  }
]