├── .github └── workflows │ ├── build.yml │ ├── e2e.yml │ └── release.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── flagger-appmesh-gateway │ └── main.go ├── docs └── appmesh-gateway-diagram.png ├── envoy.yaml ├── go.mod ├── go.sum ├── kustomize ├── base │ └── gateway │ │ ├── account.yaml │ │ ├── deployment.yaml │ │ ├── envoy.yaml │ │ ├── hpa.yaml │ │ ├── kustomization.yaml │ │ ├── rbac.yaml │ │ └── service.yaml ├── nlb │ ├── deployment.yaml │ ├── kustomization.yaml │ ├── namespace.yaml │ └── service.yaml ├── nodeport │ ├── deployment.yaml │ ├── kustomization.yaml │ ├── namespace.yaml │ └── service.yaml └── test │ ├── kustomization.yaml │ ├── namespace.yaml │ ├── podinfo │ ├── deployment.yaml │ ├── hpa.yaml │ ├── kustomization.yaml │ ├── service.yaml │ ├── virtual-node.yaml │ └── virtual-service.yaml │ └── tester │ ├── deployment.yaml │ ├── kustomization.yaml │ ├── service.yaml │ └── virtual-node.yaml ├── pkg ├── discovery │ ├── controller.go │ ├── virtualnode.go │ └── virtualservice.go ├── envoy │ ├── annotations.go │ ├── cache.go │ ├── cluster.go │ ├── hasher.go │ ├── listener.go │ ├── snapshot.go │ ├── snapshot_test.go │ ├── upstream.go │ └── virtualhost.go ├── server │ ├── callbacks.go │ └── server.go └── signals │ ├── signal.go │ ├── signal_posix.go │ └── signal_windows.go └── test ├── e2e-build.sh ├── e2e-lib.sh ├── e2e-logs.sh ├── e2e-run.sh └── gateway.bats /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Go fmt 11 | uses: stefanprodan/kube-tools@v1 12 | with: 13 | command: make go-fmt 14 | - name: Go test 15 | uses: stefanprodan/kube-tools@v1 16 | with: 17 | command: make test 18 | - name: Validate kustomization 19 | uses: stefanprodan/kube-tools@v1 20 | with: 21 | command: | 22 | echo "build gateway" 23 | kustomize build ./kustomize/nlb | kubeval --strict --ignore-missing-schemas 24 | kustomize build ./kustomize/nodeport | kubeval --strict --ignore-missing-schemas 25 | echo "build test" 26 | kustomize build ./kustomize/test | kubeval --strict --ignore-missing-schemas 27 | build: 28 | runs-on: ubuntu-latest 29 | needs: [test] 30 | steps: 31 | - uses: actions/checkout@v1 32 | - name: Build container 33 | run: make build-container 34 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: engineerd/setup-kind@v0.2.0 11 | - run: test/e2e-build.sh 12 | - run: test/e2e-run.sh 13 | - if: failure() 14 | run: test/e2e-logs.sh 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - '**' 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | push-container: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Login to Docker Hub 16 | uses: azure/container-actions/docker-login@master 17 | with: 18 | username: ${{ secrets.DOCKER_USERNAME }} 19 | password: ${{ secrets.DOCKER_PASSWORD }} 20 | - name: Push container 21 | run: | 22 | if [[ "${GITHUB_REF}" == "refs/tags"* ]]; then 23 | DOCKER_TAG=$(echo ${GITHUB_REF} | rev | cut -d/ -f1 | rev) 24 | docker build . -t weaveworks/flagger-appmesh-gateway:${DOCKER_TAG} 25 | docker push weaveworks/flagger-appmesh-gateway:${DOCKER_TAG} 26 | fi 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | bin/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13 as builder 2 | 3 | RUN mkdir -p /flagger-appmesh-gateway/ 4 | 5 | WORKDIR /flagger-appmesh-gateway 6 | 7 | COPY . . 8 | 9 | RUN go mod download 10 | 11 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o bin/flagger-appmesh-gateway cmd/flagger-appmesh-gateway/* 12 | 13 | FROM alpine:3.10 14 | 15 | RUN addgroup -S app \ 16 | && adduser -S -g app app \ 17 | && apk --no-cache add \ 18 | curl openssl netcat-openbsd 19 | 20 | WORKDIR /home/app 21 | 22 | COPY --from=builder /flagger-appmesh-gateway/bin/flagger-appmesh-gateway . 23 | RUN chown -R app:app ./ 24 | 25 | USER app 26 | 27 | CMD ["./flagger-appmesh-gateway"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TAG?=latest 2 | VERSION?=$(shell grep 'const VERSION' cmd/flagger-appmesh-gateway/main.go | awk '{ print $$4 }' | tr -d '"' | head -n1) 3 | NAME:=flagger-appmesh-gateway 4 | DOCKER_REPOSITORY:=weaveworks 5 | DOCKER_IMAGE_NAME:=$(DOCKER_REPOSITORY)/$(NAME) 6 | 7 | build: 8 | go build -o bin/flagger-appmesh-gateway cmd/flagger-appmesh-gateway/*.go 9 | 10 | test: 11 | go test -v -race ./... 12 | 13 | go-fmt: 14 | gofmt -l pkg/* | grep ".*\.go"; if [ "$$?" = "0" ]; then exit 1; fi; 15 | 16 | run: 17 | go run cmd/flagger-appmesh-gateway/*.go --kubeconfig=$$HOME/.kube/config -v=4 \ 18 | --gateway-mesh=appmesh --gateway-name=gateway --gateway-namespace=flagger-appmesh-gateway 19 | 20 | envoy: 21 | envoy -c envoy.yaml -l info 22 | 23 | build-container: 24 | docker build -t $(DOCKER_IMAGE_NAME):v$(VERSION) . 25 | 26 | push-container: build-container 27 | docker push $(DOCKER_IMAGE_NAME):v$(VERSION) 28 | 29 | version-set: 30 | @next="$(TAG)" && \ 31 | current="$(VERSION)" && \ 32 | sed -i '' "s/$$current/$$next/g" cmd/flagger-appmesh-gateway/main.go && \ 33 | sed -i '' "s/flagger-appmesh-gateway:v$$current/flagger-appmesh-gateway:v$$next/g" kustomize/base/gateway/deployment.yaml && \ 34 | echo "Version $$next set in code and kustomization" 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flagger-appmesh-gateway 2 | [![build](https://github.com/stefanprodan/flagger-appmesh-gateway/workflows/build/badge.svg)](https://github.com/stefanprodan/flagger-appmesh-gateway/actions) 3 | [![e2e](https://github.com/stefanprodan/flagger-appmesh-gateway/workflows/e2e/badge.svg)](https://github.com/stefanprodan/flagger-appmesh-gateway/actions) 4 | [![report](https://goreportcard.com/badge/github.com/stefanprodan/flagger-appmesh-gateway)](https://goreportcard.com/report/github.com/stefanprodan/flagger-appmesh-gateway) 5 | [![release](https://img.shields.io/github/release/stefanprodan/flagger-appmesh-gateway/all.svg)](https://github.com/stefanprodan/flagger-appmesh-gateway/releases) 6 | 7 | Flagger Gateway for AWS App Mesh is an edge L7 load balancer that exposes applications outside the mesh. 8 | 9 | **Note** this project has been deprecated in favour of the official [AWS AppMesh Gateway](https://aws.amazon.com/about-aws/whats-new/2020/07/aws-app-mesh-launches-ingress-support-for-virtual-gateways/). 10 | 11 | Features: 12 | * allows running canary deployments and A/B testing with [Flagger](https://flagger.app) 13 | for user-facing web applications and APIs 14 | * allows binding a public or internal domain to a mesh address 15 | * enables App Mesh client load-balancing for AWS NLB, ALB and Amazon API Gateway 16 | * allows setting retries polices and timeouts for each service 17 | * exports metrics in Prometheus format (request rate, error rate and latency) 18 | * provides access logging for ingress traffic 19 | * tags incoming requests and facilitates distributed tracing 20 | 21 | The gateway is composed of: 22 | * [Envoy](https://www.envoyproxy.io/) proxy 23 | * Envoy control plane (xDS gRPC server) 24 | * Kubernetes controller (service discovery) 25 | 26 | ![flagger-appmesh-gateway](docs/appmesh-gateway-diagram.png) 27 | 28 | An application running on App Mesh can be exposed outside the mesh by annotating its virtual service with: 29 | 30 | ```yaml 31 | apiVersion: appmesh.k8s.aws/v1beta1 32 | kind: VirtualService 33 | metadata: 34 | name: frontend.test 35 | annotations: 36 | gateway.appmesh.k8s.aws/expose: "true" 37 | gateway.appmesh.k8s.aws/retries: "5" 38 | gateway.appmesh.k8s.aws/timeout: "25s" 39 | gateway.appmesh.k8s.aws/domain: "example.com,www.example.com" 40 | ``` 41 | 42 | If you want to expose the service inside the Kubernetes cluster you can omit the domain annotation. 43 | By default the gateway exposes a virtual service by its name, 44 | a service can be accessed by setting the host HTTP header e.g.: 45 | ```sh 46 | curl -H 'Host: frontend.test' http:/// 47 | ``` 48 | 49 | The gateway registers/de-registers virtual services automatically as they come and go in the cluster. 50 | 51 | ## Install 52 | 53 | Requirements: 54 | * App Mesh CRDs, controller and inject [installed](https://github.com/aws/eks-charts#app-mesh) 55 | * A mesh called `appmesh` 56 | 57 | Install the API Gateway as NLB in `appmesh-gateway` namespace: 58 | 59 | ```sh 60 | kubectl apply -k github.com/stefanprodan/flagger-appmesh-gateway//kustomize/nlb 61 | ``` 62 | 63 | To run the gateway behind an ALB you can install the NodePort version: 64 | 65 | ```sh 66 | kubectl apply -k github.com/stefanprodan/flagger-appmesh-gateway//kustomize/nodeport 67 | ``` 68 | 69 | Wait for the deployment rollout to finish: 70 | 71 | ```sh 72 | kubectl -n appmesh-gateway rollout status deploy/flagger-appmesh-gateway 73 | ``` 74 | 75 | When the gateway starts it will create a virtual node. You can verify the install with: 76 | 77 | ```text 78 | watch kubectl -n appmesh-gateway describe virtualnode flagger-appmesh-gateway 79 | 80 | Status: 81 | Conditions: 82 | Status: True 83 | Type: VirtualNodeActive 84 | ``` 85 | 86 | ## Example 87 | 88 | Deploy podinfo in the `test` namespace: 89 | 90 | ```sh 91 | kubectl -n test apply -k github.com/stefanprodan/flagger-appmesh-gateway//kustomize/test 92 | ``` 93 | 94 | Port forward to the gateway: 95 | 96 | ```sh 97 | kubectl -n appmesh-gateway port-forward svc/flagger-appmesh-gateway 8080:80 98 | ``` 99 | 100 | Access the podinfo API by setting the host header to `podinfo.test`: 101 | 102 | ```sh 103 | curl -vH 'Host: podinfo.test' localhost:8080 104 | ``` 105 | 106 | Access podinfo on its custom domain: 107 | 108 | ```sh 109 | curl -vH 'Host: podinfo.internal' localhost:8080 110 | ``` 111 | 112 | Access podinfo using the gateway NLB address: 113 | 114 | ```sh 115 | URL="http://$(kubectl -n appmesh-gateway get svc/flagger-appmesh-gateway -ojson | \ 116 | jq -r ".status.loadBalancer.ingress[].hostname")" 117 | 118 | curl -vH 'Host: podinfo.internal' $URL 119 | ``` 120 | 121 | ## Contributing 122 | 123 | App Mesh Gateway is Apache 2.0 licensed and accepts contributions via GitHub pull requests. 124 | -------------------------------------------------------------------------------- /cmd/flagger-appmesh-gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | goflag "flag" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | flag "github.com/spf13/pflag" 12 | "k8s.io/client-go/dynamic" 13 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 14 | "k8s.io/client-go/tools/clientcmd" 15 | "k8s.io/klog" 16 | 17 | "github.com/stefanprodan/flagger-appmesh-gateway/pkg/discovery" 18 | "github.com/stefanprodan/flagger-appmesh-gateway/pkg/envoy" 19 | "github.com/stefanprodan/flagger-appmesh-gateway/pkg/server" 20 | "github.com/stefanprodan/flagger-appmesh-gateway/pkg/signals" 21 | ) 22 | 23 | // VERSION semantic versioning format 24 | const VERSION = "1.0.0" 25 | 26 | var ( 27 | masterURL string 28 | kubeConfig string 29 | port int 30 | namespace string 31 | ads bool 32 | optIn bool 33 | gatewayMesh string 34 | gatewayName string 35 | gatewayNamespace string 36 | ) 37 | 38 | func init() { 39 | pf := rootCmd.PersistentFlags() 40 | pf.StringVarP(&masterURL, "master", "", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") 41 | pf.StringVarP(&kubeConfig, "kubeconfig", "", "", "Path to a kubeconfig. Only required if out-of-cluster.") 42 | pf.IntVarP(&port, "port", "p", 18000, "Envoy xDS port to listen on.") 43 | pf.BoolVarP(&ads, "ads", "a", true, "ADS flag forces all Envoy resources to be explicitly named in the request.") 44 | pf.StringVarP(&namespace, "namespace", "n", "", "Namespace to watch for Kubernetes objects, a blank value means all namespaces.") 45 | pf.BoolVarP(&optIn, "opt-in", "", false, "When enabled only services with the 'expose' annotation will be discoverable.") 46 | pf.StringVarP(&gatewayMesh, "gateway-mesh", "", "", "App Mesh mesh that this gateway belongs to.") 47 | cobra.MarkFlagRequired(pf, "gateway-mesh") 48 | pf.StringVarP(&gatewayName, "gateway-name", "", "", "Gateway Kubernetes service name.") 49 | cobra.MarkFlagRequired(pf, "gateway-name") 50 | pf.StringVarP(&gatewayNamespace, "gateway-namespace", "", "", "Gateway Kubernetes namespace.") 51 | cobra.MarkFlagRequired(pf, "gateway-namespace") 52 | } 53 | 54 | var rootCmd = &cobra.Command{ 55 | Use: "appmesh-gateway", 56 | Long: `appmesh-gateway is responsible for exposing services outside the mesh.`, 57 | Version: VERSION, 58 | RunE: run, 59 | } 60 | 61 | func main() { 62 | flag.CommandLine.Parse([]string{}) 63 | pf := rootCmd.PersistentFlags() 64 | addKlogFlags(pf) 65 | 66 | rootCmd.SetArgs(os.Args[1:]) 67 | if err := rootCmd.Execute(); err != nil { 68 | e := err.Error() 69 | fmt.Println(strings.ToUpper(e[:1]) + e[1:]) 70 | os.Exit(1) 71 | } 72 | } 73 | 74 | func run(cmd *cobra.Command, args []string) error { 75 | cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeConfig) 76 | if err != nil { 77 | klog.Fatalf("error building kubeconfig: %v", err) 78 | } 79 | 80 | client, err := dynamic.NewForConfig(cfg) 81 | if err != nil { 82 | klog.Fatalf("error building kubernetes client: %v", err) 83 | } 84 | 85 | stopCh := signals.SetupSignalHandler() 86 | ctx := context.Background() 87 | cache := envoy.NewCache(ads) 88 | snapshot := envoy.NewSnapshot(cache) 89 | 90 | klog.Infof("starting xDS server on port %d", port) 91 | srv := server.NewServer(port, cache) 92 | go srv.Serve(ctx) 93 | 94 | klog.Info("waiting for Envoy to connect to the xDS server") 95 | srv.Report() 96 | 97 | vnManager := discovery.NewVirtualNodeManager(client, gatewayMesh, gatewayName, gatewayNamespace) 98 | if err := vnManager.CheckAccess(); err != nil { 99 | klog.Fatalf("the gateway can't read App Mesh objects, check RBAC, error %v", err) 100 | } 101 | 102 | vsManager := discovery.NewVirtualServiceManager(client, optIn) 103 | kd := discovery.NewController(client, namespace, snapshot, vsManager, vnManager) 104 | 105 | klog.Info("starting App Mesh discovery workers") 106 | kd.Run(2, stopCh) 107 | 108 | return nil 109 | } 110 | 111 | func addKlogFlags(fs *flag.FlagSet) { 112 | local := goflag.NewFlagSet(os.Args[0], goflag.ExitOnError) 113 | klog.InitFlags(local) 114 | local.VisitAll(func(fl *goflag.Flag) { 115 | fl.Name = normalizeFlag(fl.Name) 116 | fs.AddGoFlag(fl) 117 | }) 118 | } 119 | 120 | func normalizeFlag(s string) string { 121 | return strings.Replace(s, "_", "-", -1) 122 | } 123 | -------------------------------------------------------------------------------- /docs/appmesh-gateway-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/flagger-appmesh-gateway/b266659268d7bf434933922bf02bf76a8d66c0a9/docs/appmesh-gateway-diagram.png -------------------------------------------------------------------------------- /envoy.yaml: -------------------------------------------------------------------------------- 1 | node: 2 | cluster: envoy 3 | id: envoy 4 | 5 | admin: 6 | access_log_path: /dev/null 7 | address: 8 | socket_address: 9 | address: 0.0.0.0 10 | port_value: 8081 11 | 12 | dynamic_resources: 13 | ads_config: 14 | api_type: GRPC 15 | grpc_services: 16 | - envoy_grpc: 17 | cluster_name: xds 18 | cds_config: 19 | ads: {} 20 | lds_config: 21 | ads: {} 22 | 23 | static_resources: 24 | clusters: 25 | - name: xds 26 | connect_timeout: 0.50s 27 | type: static 28 | http2_protocol_options: {} 29 | load_assignment: 30 | cluster_name: xds 31 | endpoints: 32 | - lb_endpoints: 33 | - endpoint: 34 | address: 35 | socket_address: 36 | address: 127.0.0.1 37 | port_value: 18000 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stefanprodan/flagger-appmesh-gateway 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aws/aws-app-mesh-controller-for-k8s v0.2.0 7 | github.com/envoyproxy/go-control-plane v0.9.0 8 | github.com/gogo/protobuf v1.3.1 // indirect 9 | github.com/golang/protobuf v1.3.2 10 | github.com/googleapis/gnostic v0.3.1 // indirect 11 | github.com/imdario/mergo v0.3.8 // indirect 12 | github.com/mitchellh/hashstructure v1.0.0 13 | github.com/spf13/cobra v0.0.5 14 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect 15 | google.golang.org/grpc v1.23.0 16 | k8s.io/api v0.0.0-20191025225708-5524a3672fbb // indirect 17 | k8s.io/apimachinery v0.0.0-20191025225532-af6325b3a843 18 | k8s.io/client-go v11.0.0+incompatible 19 | k8s.io/klog v1.0.0 20 | k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 // indirect 21 | ) 22 | 23 | // Kubernetes 1.15.0 24 | replace ( 25 | k8s.io/api => k8s.io/api v0.0.0-20191025225708-5524a3672fbb 26 | k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191025225532-af6325b3a843 27 | k8s.io/client-go => k8s.io/client-go v0.0.0-20190620085101-78d2af792bab 28 | k8s.io/utils => k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= 4 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 5 | github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 8 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 9 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 10 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 11 | github.com/aws/aws-app-mesh-controller-for-k8s v0.2.0 h1:NyjQ0tW8e5VrHb2Cfx2sEGwn3rnrp3UG5ZMA2fECXZ8= 12 | github.com/aws/aws-app-mesh-controller-for-k8s v0.2.0/go.mod h1:5142S0va+4HY5qBlqbKKpo68Igtibc3TlRSgCd4Wrco= 13 | github.com/aws/aws-sdk-go v1.23.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 14 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 15 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 16 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 17 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 18 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 19 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 20 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 21 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 26 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 27 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 28 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 29 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 30 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 31 | github.com/envoyproxy/go-control-plane v0.9.0 h1:67WMNTvGrl7V1dWdKCeTwxDr7nio9clKoTlLhwIPnT4= 32 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 33 | github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= 34 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 35 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 36 | github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= 37 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 38 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 39 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 40 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 41 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 42 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 43 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 44 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 45 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 46 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 47 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 48 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 49 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 50 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 51 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 52 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 53 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 54 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 55 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= 56 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 57 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= 58 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 59 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 60 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 61 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 62 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 63 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 64 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 65 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 66 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 67 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 68 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 69 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 70 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 71 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 72 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 73 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 74 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 75 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 76 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 77 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 78 | github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 79 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 80 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 81 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 82 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 83 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 84 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 85 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 86 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 87 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 88 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 89 | github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= 90 | github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 91 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 92 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 93 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 94 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 95 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 96 | github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= 97 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 98 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 99 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 100 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 101 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 102 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 103 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 104 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 105 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 106 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 107 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 108 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 109 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 110 | github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= 111 | github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= 112 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 113 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 114 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 115 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 116 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 117 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 118 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 119 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 120 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 121 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 122 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 123 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 124 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 125 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 126 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 127 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 128 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 129 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 130 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 131 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 132 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 133 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 134 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 135 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 136 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 137 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 138 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 139 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 140 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 141 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 142 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 143 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 144 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 145 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 146 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 147 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 148 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 149 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 150 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 151 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 152 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 153 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 154 | github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 155 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 156 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 157 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 158 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 159 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 160 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 161 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 162 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 163 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 164 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 165 | github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5/go.mod h1:ppEjwdhyy7Y31EnHRDm1JkChoC7LXIJ7Ex0VYLWtZtQ= 166 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 167 | golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 168 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 169 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 170 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 171 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 172 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 173 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 174 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 175 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 176 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 177 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 178 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 179 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 180 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 181 | golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 182 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 183 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 184 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 185 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 186 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= 187 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 188 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 189 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 190 | golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 191 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= 192 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 193 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 197 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 198 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 199 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 200 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 201 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 202 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 203 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= 204 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 206 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 207 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 208 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 209 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 210 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 211 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 212 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 213 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 214 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 215 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 216 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 217 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 218 | golang.org/x/tools v0.0.0-20181112210238-4b1f3b6b1646/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 219 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 220 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 221 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 222 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 223 | golang.org/x/tools v0.0.0-20190710153321-831012c29e42/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 224 | golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY= 225 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 226 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 227 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 228 | google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 229 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 230 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 231 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 232 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 233 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 234 | google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= 235 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 236 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 237 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 238 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 239 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 240 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 241 | gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= 242 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 243 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 244 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 245 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 246 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 247 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 248 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 249 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 250 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 251 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 252 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 253 | k8s.io/api v0.0.0-20191025225708-5524a3672fbb h1:XpnI7Pjlb0pMfI0hmHgWgaKqkp/JIL3JYYFN21AYCP8= 254 | k8s.io/api v0.0.0-20191025225708-5524a3672fbb/go.mod h1:NMIXwlJTrA+pXie6lv562GUPkluJ4oRGzQfqWBLaceY= 255 | k8s.io/apimachinery v0.0.0-20191025225532-af6325b3a843 h1:Ge6Np+ecN6pKYcAaFXR53I88+UL5ch+KpLxkKREpsJ4= 256 | k8s.io/apimachinery v0.0.0-20191025225532-af6325b3a843/go.mod h1:gA1T9z4LIup7PIegBwxkF2UYXUNVKhOAPvQWWnAc34k= 257 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g= 258 | k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k= 259 | k8s.io/code-generator v0.0.0-20190311155051-e4c2b1329cf7/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8= 260 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 261 | k8s.io/gengo v0.0.0-20190308184658-b90029ef6cd8/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 262 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 263 | k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 264 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 265 | k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 266 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 267 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 268 | k8s.io/kube-openapi v0.0.0-20190222203931-aa8624f5a2df/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 269 | k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ= 270 | k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 271 | k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 h1:Gi+/O1saihwDqnlmC8Vhv1M5Sp4+rbOmK9TbsLn8ZEA= 272 | k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 273 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 274 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 275 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 276 | -------------------------------------------------------------------------------- /kustomize/base/gateway/account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | -------------------------------------------------------------------------------- /kustomize/base/gateway/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | labels: 6 | app: flagger-appmesh-gateway 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: flagger-appmesh-gateway 11 | strategy: 12 | type: RollingUpdate 13 | rollingUpdate: 14 | maxUnavailable: 0 15 | template: 16 | metadata: 17 | labels: 18 | app: flagger-appmesh-gateway 19 | annotations: 20 | prometheus.io/scrape: "true" 21 | prometheus.io/path: "/stats/prometheus" 22 | prometheus.io/port: "8081" 23 | # exclude inbound traffic on port 8080 24 | appmesh.k8s.aws/ports: "444" 25 | # exclude egress traffic to xDS server and Kubernetes API 26 | appmesh.k8s.aws/egressIgnoredPorts: "18000,22,443" 27 | spec: 28 | serviceAccountName: flagger-appmesh-gateway 29 | terminationGracePeriodSeconds: 45 30 | affinity: 31 | podAntiAffinity: 32 | preferredDuringSchedulingIgnoredDuringExecution: 33 | - podAffinityTerm: 34 | labelSelector: 35 | matchLabels: 36 | app: flagger-appmesh-gateway 37 | topologyKey: kubernetes.io/hostname 38 | weight: 100 39 | containers: 40 | - name: proxy 41 | image: docker.io/envoyproxy/envoy:v1.12.1 42 | imagePullPolicy: IfNotPresent 43 | securityContext: 44 | capabilities: 45 | drop: 46 | - ALL 47 | add: 48 | - NET_BIND_SERVICE 49 | args: 50 | - -c 51 | - /config/envoy.yaml 52 | - --service-cluster $(POD_NAMESPACE) 53 | - --service-node $(POD_NAME) 54 | - --log-level info 55 | - --base-id 1234 56 | ports: 57 | - name: admin 58 | containerPort: 8081 59 | protocol: TCP 60 | - name: http 61 | containerPort: 8080 62 | protocol: TCP 63 | livenessProbe: 64 | initialDelaySeconds: 5 65 | tcpSocket: 66 | port: admin 67 | readinessProbe: 68 | initialDelaySeconds: 5 69 | httpGet: 70 | path: /ready 71 | port: admin 72 | env: 73 | - name: POD_NAME 74 | valueFrom: 75 | fieldRef: 76 | fieldPath: metadata.name 77 | - name: POD_NAMESPACE 78 | valueFrom: 79 | fieldRef: 80 | fieldPath: metadata.namespace 81 | resources: 82 | limits: 83 | memory: 1Gi 84 | requests: 85 | cpu: 100m 86 | memory: 128Mi 87 | volumeMounts: 88 | - name: flagger-appmesh-gateway 89 | mountPath: /config 90 | - name: controller 91 | image: docker.io/weaveworks/flagger-appmesh-gateway:v1.0.0 92 | imagePullPolicy: IfNotPresent 93 | securityContext: 94 | readOnlyRootFilesystem: true 95 | runAsUser: 10001 96 | capabilities: 97 | drop: 98 | - ALL 99 | add: 100 | - NET_BIND_SERVICE 101 | command: 102 | - ./flagger-appmesh-gateway 103 | - --gateway-mesh=appmesh 104 | - --gateway-name=$(POD_SERVICE_ACCOUNT) 105 | - --gateway-namespace=$(POD_NAMESPACE) 106 | env: 107 | - name: POD_SERVICE_ACCOUNT 108 | valueFrom: 109 | fieldRef: 110 | fieldPath: spec.serviceAccountName 111 | - name: POD_NAMESPACE 112 | valueFrom: 113 | fieldRef: 114 | fieldPath: metadata.namespace 115 | ports: 116 | - name: grpc 117 | containerPort: 18000 118 | protocol: TCP 119 | livenessProbe: 120 | initialDelaySeconds: 5 121 | tcpSocket: 122 | port: grpc 123 | readinessProbe: 124 | initialDelaySeconds: 5 125 | tcpSocket: 126 | port: grpc 127 | resources: 128 | limits: 129 | memory: 1Gi 130 | requests: 131 | cpu: 10m 132 | memory: 32Mi 133 | volumes: 134 | - name: flagger-appmesh-gateway 135 | configMap: 136 | name: flagger-appmesh-gateway 137 | -------------------------------------------------------------------------------- /kustomize/base/gateway/envoy.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /dev/null 3 | address: 4 | socket_address: 5 | address: 0.0.0.0 6 | port_value: 8081 7 | 8 | dynamic_resources: 9 | ads_config: 10 | api_type: GRPC 11 | grpc_services: 12 | - envoy_grpc: 13 | cluster_name: xds 14 | cds_config: 15 | ads: {} 16 | lds_config: 17 | ads: {} 18 | 19 | static_resources: 20 | clusters: 21 | - name: xds 22 | connect_timeout: 0.50s 23 | type: static 24 | http2_protocol_options: {} 25 | load_assignment: 26 | cluster_name: xds 27 | endpoints: 28 | - lb_endpoints: 29 | - endpoint: 30 | address: 31 | socket_address: 32 | address: 127.0.0.1 33 | port_value: 18000 34 | -------------------------------------------------------------------------------- /kustomize/base/gateway/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2beta1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: flagger-appmesh-gateway 10 | minReplicas: 1 11 | maxReplicas: 3 12 | metrics: 13 | - type: Resource 14 | resource: 15 | name: cpu 16 | targetAverageUtilization: 99 17 | -------------------------------------------------------------------------------- /kustomize/base/gateway/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - account.yaml 5 | - deployment.yaml 6 | - service.yaml 7 | - hpa.yaml 8 | - rbac.yaml 9 | configMapGenerator: 10 | - name: flagger-appmesh-gateway 11 | files: 12 | - envoy.yaml 13 | -------------------------------------------------------------------------------- /kustomize/base/gateway/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - services 10 | verbs: ["*"] 11 | - apiGroups: 12 | - appmesh.k8s.aws 13 | resources: 14 | - meshes 15 | - meshes/status 16 | - virtualnodes 17 | - virtualnodes/status 18 | - virtualservices 19 | - virtualservices/status 20 | verbs: ["*"] 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1beta1 23 | kind: ClusterRoleBinding 24 | metadata: 25 | name: flagger-appmesh-gateway 26 | roleRef: 27 | apiGroup: rbac.authorization.k8s.io 28 | kind: ClusterRole 29 | name: flagger-appmesh-gateway 30 | subjects: 31 | - kind: ServiceAccount 32 | name: flagger-appmesh-gateway 33 | namespace: appmesh-gateway 34 | -------------------------------------------------------------------------------- /kustomize/base/gateway/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | labels: 6 | app: flagger-appmesh-gateway 7 | annotations: 8 | gateway.appmesh.k8s.aws/expose: "false" 9 | spec: 10 | type: NodePort 11 | ports: 12 | - name: http 13 | port: 80 14 | protocol: TCP 15 | targetPort: http 16 | selector: 17 | app: flagger-appmesh-gateway 18 | -------------------------------------------------------------------------------- /kustomize/nlb/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: controller 10 | command: 11 | - ./flagger-appmesh-gateway 12 | - --opt-in=true 13 | - --gateway-mesh=appmesh 14 | - --gateway-name=$(POD_SERVICE_ACCOUNT) 15 | - --gateway-namespace=$(POD_NAMESPACE) 16 | -------------------------------------------------------------------------------- /kustomize/nlb/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: appmesh-gateway 4 | bases: 5 | - ../base/gateway 6 | resources: 7 | - namespace.yaml 8 | patchesStrategicMerge: 9 | - deployment.yaml 10 | - service.yaml 11 | -------------------------------------------------------------------------------- /kustomize/nlb/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: appmesh-gateway 5 | labels: 6 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 7 | -------------------------------------------------------------------------------- /kustomize/nlb/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | annotations: 6 | service.beta.kubernetes.io/aws-load-balancer-type: "nlb" 7 | spec: 8 | type: LoadBalancer 9 | externalTrafficPolicy: Local 10 | -------------------------------------------------------------------------------- /kustomize/nodeport/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: controller 10 | command: 11 | - ./flagger-appmesh-gateway 12 | - --opt-in=true 13 | - --gateway-mesh=appmesh 14 | - --gateway-name=$(POD_SERVICE_ACCOUNT) 15 | - --gateway-namespace=$(POD_NAMESPACE) 16 | -------------------------------------------------------------------------------- /kustomize/nodeport/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: appmesh-gateway 4 | bases: 5 | - ../base/gateway 6 | resources: 7 | - namespace.yaml 8 | patchesStrategicMerge: 9 | - deployment.yaml 10 | - service.yaml 11 | -------------------------------------------------------------------------------- /kustomize/nodeport/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: appmesh-gateway 5 | labels: 6 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 7 | -------------------------------------------------------------------------------- /kustomize/nodeport/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: flagger-appmesh-gateway 5 | spec: 6 | type: NodePort 7 | -------------------------------------------------------------------------------- /kustomize/test/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: test 4 | bases: 5 | - podinfo 6 | - tester 7 | resources: 8 | - namespace.yaml 9 | -------------------------------------------------------------------------------- /kustomize/test/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: test 5 | labels: 6 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 7 | -------------------------------------------------------------------------------- /kustomize/test/podinfo/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: podinfo 5 | labels: 6 | app: podinfo 7 | spec: 8 | minReadySeconds: 3 9 | revisionHistoryLimit: 5 10 | progressDeadlineSeconds: 60 11 | strategy: 12 | rollingUpdate: 13 | maxUnavailable: 0 14 | type: RollingUpdate 15 | selector: 16 | matchLabels: 17 | app: podinfo 18 | template: 19 | metadata: 20 | annotations: 21 | prometheus.io/scrape: "true" 22 | prometheus.io/port: "9797" 23 | # # Include only the http port in the mesh 24 | # appmesh.k8s.aws/ports: "9898" 25 | labels: 26 | app: podinfo 27 | spec: 28 | containers: 29 | - name: podinfod 30 | image: stefanprodan/podinfo:3.1.0 31 | imagePullPolicy: IfNotPresent 32 | ports: 33 | - name: http 34 | containerPort: 9898 35 | protocol: TCP 36 | - name: http-metrics 37 | containerPort: 9797 38 | protocol: TCP 39 | - name: grpc 40 | containerPort: 9999 41 | protocol: TCP 42 | command: 43 | - ./podinfo 44 | - --port=9898 45 | - --port-metrics=9797 46 | - --grpc-port=9999 47 | - --grpc-service-name=podinfo 48 | - --level=info 49 | - --random-delay=false 50 | - --random-error=false 51 | env: 52 | - name: PODINFO_UI_COLOR 53 | value: cyan 54 | livenessProbe: 55 | exec: 56 | command: 57 | - podcli 58 | - check 59 | - http 60 | - localhost:9898/healthz 61 | initialDelaySeconds: 5 62 | timeoutSeconds: 5 63 | readinessProbe: 64 | exec: 65 | command: 66 | - podcli 67 | - check 68 | - http 69 | - localhost:9898/readyz 70 | initialDelaySeconds: 5 71 | timeoutSeconds: 5 72 | resources: 73 | limits: 74 | cpu: 2000m 75 | memory: 512Mi 76 | requests: 77 | cpu: 100m 78 | memory: 64Mi 79 | -------------------------------------------------------------------------------- /kustomize/test/podinfo/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2beta1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: podinfo 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: podinfo 10 | minReplicas: 2 11 | maxReplicas: 4 12 | metrics: 13 | - type: Resource 14 | resource: 15 | name: cpu 16 | # scale up if usage is above 17 | # 99% of the requested CPU (100m) 18 | targetAverageUtilization: 99 19 | -------------------------------------------------------------------------------- /kustomize/test/podinfo/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - hpa.yaml 5 | - deployment.yaml 6 | - service.yaml 7 | - virtual-node.yaml 8 | - virtual-service.yaml 9 | 10 | 11 | -------------------------------------------------------------------------------- /kustomize/test/podinfo/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: podinfo 5 | labels: 6 | app: podinfo 7 | spec: 8 | type: ClusterIP 9 | selector: 10 | app: podinfo 11 | ports: 12 | - name: http 13 | port: 9898 14 | protocol: TCP 15 | targetPort: http 16 | -------------------------------------------------------------------------------- /kustomize/test/podinfo/virtual-node.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appmesh.k8s.aws/v1beta1 2 | kind: VirtualNode 3 | metadata: 4 | name: podinfo 5 | spec: 6 | meshName: appmesh 7 | serviceDiscovery: 8 | dns: 9 | hostName: podinfo.test 10 | listeners: 11 | - portMapping: 12 | port: 9898 13 | protocol: http 14 | 15 | -------------------------------------------------------------------------------- /kustomize/test/podinfo/virtual-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appmesh.k8s.aws/v1beta1 2 | kind: VirtualService 3 | metadata: 4 | name: podinfo.test 5 | annotations: 6 | gateway.appmesh.k8s.aws/expose: "true" 7 | gateway.appmesh.k8s.aws/timeout: "25s" 8 | gateway.appmesh.k8s.aws/retries: "5" 9 | gateway.appmesh.k8s.aws/domain: "podinfo.internal" 10 | spec: 11 | meshName: appmesh 12 | virtualRouter: 13 | name: podinfo 14 | listeners: 15 | - portMapping: 16 | port: 9898 17 | protocol: http 18 | routes: 19 | - name: podinfo 20 | http: 21 | action: 22 | weightedTargets: 23 | - virtualNodeName: podinfo 24 | weight: 100 25 | match: 26 | prefix: / 27 | -------------------------------------------------------------------------------- /kustomize/test/tester/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: tester 5 | labels: 6 | app: tester 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: tester 11 | template: 12 | metadata: 13 | labels: 14 | app: tester 15 | annotations: 16 | prometheus.io/scrape: "true" 17 | prometheus.io/port: "8080" 18 | # Exclude ingress from the mesh 19 | appmesh.k8s.aws/ports: "444" 20 | spec: 21 | containers: 22 | - name: tester 23 | image: weaveworks/flagger-loadtester:0.10.0 24 | imagePullPolicy: IfNotPresent 25 | ports: 26 | - name: http 27 | containerPort: 8080 28 | command: 29 | - ./loadtester 30 | - -port=8080 31 | - -log-level=info 32 | - -timeout=1h 33 | livenessProbe: 34 | exec: 35 | command: 36 | - wget 37 | - --quiet 38 | - --tries=1 39 | - --timeout=4 40 | - --spider 41 | - http://localhost:8080/healthz 42 | timeoutSeconds: 5 43 | readinessProbe: 44 | exec: 45 | command: 46 | - wget 47 | - --quiet 48 | - --tries=1 49 | - --timeout=4 50 | - --spider 51 | - http://localhost:8080/healthz 52 | timeoutSeconds: 5 53 | resources: 54 | limits: 55 | memory: "512Mi" 56 | cpu: "1000m" 57 | requests: 58 | memory: "32Mi" 59 | cpu: "10m" 60 | securityContext: 61 | readOnlyRootFilesystem: true 62 | runAsUser: 10001 63 | -------------------------------------------------------------------------------- /kustomize/test/tester/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - service.yaml 5 | - deployment.yaml 6 | - virtual-node.yaml 7 | 8 | -------------------------------------------------------------------------------- /kustomize/test/tester/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: tester 5 | labels: 6 | app: tester 7 | annotations: 8 | gateway.appmesh.k8s.aws/expose: "false" 9 | spec: 10 | type: ClusterIP 11 | selector: 12 | app: tester 13 | ports: 14 | - name: http 15 | port: 80 16 | protocol: TCP 17 | targetPort: http 18 | -------------------------------------------------------------------------------- /kustomize/test/tester/virtual-node.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appmesh.k8s.aws/v1beta1 2 | kind: VirtualNode 3 | metadata: 4 | name: tester 5 | spec: 6 | meshName: appmesh 7 | serviceDiscovery: 8 | dns: 9 | hostName: tester.test 10 | listeners: 11 | - portMapping: 12 | port: 444 13 | protocol: http 14 | backends: 15 | - virtualService: 16 | virtualServiceName: podinfo 17 | - virtualService: 18 | virtualServiceName: appmesh-gateway 19 | -------------------------------------------------------------------------------- /pkg/discovery/controller.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 8 | "k8s.io/apimachinery/pkg/runtime/schema" 9 | "k8s.io/apimachinery/pkg/util/runtime" 10 | "k8s.io/apimachinery/pkg/util/wait" 11 | "k8s.io/client-go/dynamic" 12 | "k8s.io/client-go/dynamic/dynamicinformer" 13 | "k8s.io/client-go/tools/cache" 14 | "k8s.io/client-go/util/workqueue" 15 | "k8s.io/klog" 16 | 17 | "github.com/stefanprodan/flagger-appmesh-gateway/pkg/envoy" 18 | ) 19 | 20 | // Controller watches Kubernetes for App Mesh virtual services and 21 | // creates or deletes Envoy clusters and virtual hosts 22 | type Controller struct { 23 | client dynamic.Interface 24 | indexer cache.Indexer 25 | queue workqueue.RateLimitingInterface 26 | informer cache.Controller 27 | snapshot *envoy.Snapshot 28 | vsManager *VirtualServiceManager 29 | vnManager *VirtualNodeManager 30 | } 31 | 32 | // NewController reconciles the App Mesh virtual services with Envoy clusters and virtual hosts 33 | func NewController(client dynamic.Interface, namespace string, snapshot *envoy.Snapshot, vsManager *VirtualServiceManager, vnManager *VirtualNodeManager) *Controller { 34 | queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) 35 | factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(client, 0, namespace, nil) 36 | gvr, _ := schema.ParseResourceArg("virtualservices.v1beta1.appmesh.k8s.aws") 37 | vsFactory := factory.ForResource(*gvr) 38 | informer := vsFactory.Informer() 39 | indexer := vsFactory.Informer().GetIndexer() 40 | handlers := cache.ResourceEventHandlerFuncs{ 41 | AddFunc: func(obj interface{}) { 42 | key, err := cache.MetaNamespaceKeyFunc(obj) 43 | if err == nil { 44 | queue.Add(key) 45 | } 46 | }, 47 | UpdateFunc: func(oldObj, obj interface{}) { 48 | key, err := cache.MetaNamespaceKeyFunc(obj) 49 | if err == nil { 50 | queue.Add(key) 51 | } 52 | }, 53 | DeleteFunc: func(obj interface{}) { 54 | key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 55 | if err == nil { 56 | queue.Add(key) 57 | } 58 | }, 59 | } 60 | informer.AddEventHandler(handlers) 61 | 62 | return &Controller{ 63 | client: client, 64 | informer: informer, 65 | indexer: indexer, 66 | queue: queue, 67 | snapshot: snapshot, 68 | vsManager: vsManager, 69 | vnManager: vnManager, 70 | } 71 | } 72 | 73 | // Run starts the App Mesh discovery controller 74 | func (ctrl *Controller) Run(threadiness int, stopCh <-chan struct{}) { 75 | defer runtime.HandleCrash() 76 | defer ctrl.queue.ShutDown() 77 | 78 | go ctrl.informer.Run(stopCh) 79 | 80 | if !cache.WaitForCacheSync(stopCh, ctrl.informer.HasSynced) { 81 | runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) 82 | return 83 | } 84 | 85 | ctrl.syncAll() 86 | 87 | for i := 0; i < threadiness; i++ { 88 | go wait.Until(ctrl.runWorker, time.Second, stopCh) 89 | } 90 | 91 | tickChan := time.NewTicker(5 * time.Minute).C 92 | for { 93 | select { 94 | case <-tickChan: 95 | ctrl.syncAll() 96 | case <-stopCh: 97 | klog.Info("stopping Kubernetes discovery workers") 98 | return 99 | } 100 | } 101 | } 102 | 103 | func (ctrl *Controller) sync(key string) error { 104 | _, exists, err := ctrl.indexer.GetByKey(key) 105 | if err != nil { 106 | klog.Errorf("fetching object with key %s from store failed %v", key, err) 107 | return err 108 | } 109 | 110 | if !exists { 111 | klog.Infof("deleting %s from cache", key) 112 | ctrl.snapshot.Delete(key) 113 | } 114 | 115 | ctrl.syncAll() 116 | return nil 117 | } 118 | 119 | func (ctrl *Controller) syncAll() { 120 | var backends []string 121 | for _, value := range ctrl.indexer.List() { 122 | un := value.(*unstructured.Unstructured) 123 | vs, err := ctrl.vsManager.VirtualServiceFromUnstructured(un) 124 | if err != nil { 125 | klog.Errorf("unmarshal object %s from store failed %v", un.GetName(), err) 126 | return 127 | } 128 | if ctrl.vsManager.IsValid(*vs) { 129 | backends = append(backends, vs.Name) 130 | ctrl.snapshot.Store(fmt.Sprintf("%s/%s", vs.Namespace, vs.Name), ctrl.vsManager.ConvertToUpstream(*vs)) 131 | } 132 | } 133 | 134 | err := ctrl.vnManager.Reconcile(backends) 135 | if err != nil { 136 | klog.Error(err) 137 | return 138 | } 139 | 140 | err = ctrl.snapshot.Sync() 141 | if err != nil { 142 | klog.Errorf("snapshot error %v", err) 143 | return 144 | } 145 | } 146 | 147 | func (ctrl *Controller) handleErr(err error, key interface{}) { 148 | if err == nil { 149 | ctrl.queue.Forget(key) 150 | return 151 | } 152 | 153 | if ctrl.queue.NumRequeues(key) < 5 { 154 | klog.Infof("error syncing %v: %v", key, err) 155 | ctrl.queue.AddRateLimited(key) 156 | return 157 | } 158 | 159 | ctrl.queue.Forget(key) 160 | runtime.HandleError(err) 161 | klog.Infof("dropping %q out of the queue: %v", key, err) 162 | } 163 | 164 | func (ctrl *Controller) processNextItem() bool { 165 | key, quit := ctrl.queue.Get() 166 | if quit { 167 | return false 168 | } 169 | defer ctrl.queue.Done(key) 170 | 171 | err := ctrl.sync(key.(string)) 172 | ctrl.handleErr(err, key) 173 | return true 174 | } 175 | 176 | func (ctrl *Controller) runWorker() { 177 | for ctrl.processNextItem() { 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /pkg/discovery/virtualnode.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "fmt" 5 | 6 | appmeshv1 "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/apis/appmesh/v1beta1" 7 | "k8s.io/apimachinery/pkg/api/errors" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | "k8s.io/client-go/dynamic" 12 | "k8s.io/client-go/util/retry" 13 | "k8s.io/klog" 14 | ) 15 | 16 | // VirtualNodeManager reconciles the gateway virtual node backend 17 | type VirtualNodeManager struct { 18 | client dynamic.Interface 19 | gatewayMesh string 20 | gatewayName string 21 | gatewayNamespace string 22 | } 23 | 24 | // NewVirtualNodeManager creates an App Mesh virtual node manager 25 | func NewVirtualNodeManager(client dynamic.Interface, gatewayMesh string, gatewayName string, gatewayNamespace string) *VirtualNodeManager { 26 | return &VirtualNodeManager{ 27 | client: client, 28 | gatewayMesh: gatewayMesh, 29 | gatewayName: gatewayName, 30 | gatewayNamespace: gatewayNamespace, 31 | } 32 | } 33 | 34 | // Reconcile creates or updates the virtual node and its backends 35 | func (vnm *VirtualNodeManager) Reconcile(backends []string) error { 36 | vnName := vnm.gatewayName 37 | var vnBackends []appmeshv1.Backend 38 | for _, value := range backends { 39 | vnBackends = append(vnBackends, appmeshv1.Backend{ 40 | VirtualService: appmeshv1.VirtualServiceBackend{VirtualServiceName: value}, 41 | }) 42 | } 43 | spec := appmeshv1.VirtualNodeSpec{ 44 | MeshName: vnm.gatewayMesh, 45 | Listeners: []appmeshv1.Listener{ 46 | {PortMapping: appmeshv1.PortMapping{ 47 | Port: 444, 48 | Protocol: "http", 49 | }}, 50 | }, 51 | ServiceDiscovery: &appmeshv1.ServiceDiscovery{ 52 | Dns: &appmeshv1.DnsServiceDiscovery{ 53 | HostName: fmt.Sprintf("%s.%s", vnm.gatewayName, vnm.gatewayNamespace), 54 | }}, 55 | Backends: vnBackends, 56 | } 57 | 58 | vn := &unstructured.Unstructured{ 59 | Object: map[string]interface{}{ 60 | "kind": "VirtualNode", 61 | "apiVersion": appmeshv1.SchemeGroupVersion.String(), 62 | "metadata": map[string]interface{}{ 63 | "name": vnName, 64 | }, 65 | "spec": spec, 66 | }, 67 | } 68 | 69 | client := vnm.client.Resource(schema.GroupVersionResource{ 70 | Group: "appmesh.k8s.aws", 71 | Version: "v1beta1", 72 | Resource: "virtualnodes", 73 | }) 74 | 75 | _, err := client.Namespace(vnm.gatewayNamespace).Get(vnName, metav1.GetOptions{}) 76 | if errors.IsNotFound(err) { 77 | _, createErr := client.Namespace(vnm.gatewayNamespace).Create(vn, metav1.CreateOptions{}) 78 | if createErr != nil && !errors.IsNotFound(createErr) { 79 | return fmt.Errorf("failed to create gateway virtual node: %v", err) 80 | } 81 | return nil 82 | } 83 | 84 | if err != nil { 85 | return fmt.Errorf("failed to get gateway virtual node: %v", err) 86 | } 87 | 88 | retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { 89 | gw, err := client.Namespace(vnm.gatewayNamespace).Get(vnName, metav1.GetOptions{}) 90 | if err != nil { 91 | return err 92 | } 93 | vn = &unstructured.Unstructured{ 94 | Object: map[string]interface{}{ 95 | "kind": "VirtualNode", 96 | "apiVersion": "appmesh.k8s.aws/v1beta1", 97 | "metadata": map[string]interface{}{ 98 | "name": vnName, 99 | "resourceVersion": gw.GetResourceVersion(), 100 | }, 101 | "spec": spec, 102 | }, 103 | } 104 | _, err = client.Namespace(vnm.gatewayNamespace).Update(vn, metav1.UpdateOptions{}) 105 | if err != nil { 106 | return err 107 | } 108 | return nil 109 | }) 110 | if retryErr != nil { 111 | return fmt.Errorf("failed to update gateway virtual node: %v", retryErr) 112 | } 113 | 114 | klog.Infof("virtual node %s updated with %d backends", vnName, len(backends)) 115 | 116 | return nil 117 | } 118 | 119 | // CheckAccess verifies if RBAC is allowing virtual nodes read operations 120 | func (vnm *VirtualNodeManager) CheckAccess() error { 121 | vnr, _ := schema.ParseResourceArg("virtualnodes.v1beta1.appmesh.k8s.aws") 122 | _, err := vnm.client.Resource(*vnr).Namespace(vnm.gatewayNamespace).List(metav1.ListOptions{}) 123 | if err != nil { 124 | return err 125 | } 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /pkg/discovery/virtualservice.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | appmeshv1 "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/apis/appmesh/v1beta1" 11 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 | "k8s.io/client-go/dynamic" 13 | 14 | "github.com/stefanprodan/flagger-appmesh-gateway/pkg/envoy" 15 | ) 16 | 17 | // VirtualServiceManager transforms virtual service to upstreams 18 | type VirtualServiceManager struct { 19 | client dynamic.Interface 20 | optIn bool 21 | } 22 | 23 | // NewVirtualServiceManager creates an App Mesh virtual service manager 24 | func NewVirtualServiceManager(client dynamic.Interface, optIn bool) *VirtualServiceManager { 25 | return &VirtualServiceManager{ 26 | client: client, 27 | optIn: optIn, 28 | } 29 | } 30 | 31 | // ConvertToUpstream converts the App Mesh virtual service to an Upstream 32 | func (vsm *VirtualServiceManager) ConvertToUpstream(vs appmeshv1.VirtualService) envoy.Upstream { 33 | port := uint32(80) 34 | for _, value := range vs.Spec.VirtualRouter.Listeners { 35 | port = uint32(value.PortMapping.Port) 36 | } 37 | 38 | up := envoy.Upstream{ 39 | Name: fmt.Sprintf("%s-%d", vs.Name, port), 40 | Domains: []string{ 41 | vs.Name, 42 | fmt.Sprintf("%s:%d", vs.Name, port), 43 | }, 44 | Port: port, 45 | Host: vs.Name, 46 | Prefix: "/", 47 | Retries: 2, 48 | Timeout: 45 * time.Second, 49 | } 50 | 51 | appendDomain := func(slice []string, i string) []string { 52 | for _, ele := range slice { 53 | if ele == i { 54 | return slice 55 | } 56 | } 57 | return append(slice, i) 58 | } 59 | 60 | for key, value := range vs.Annotations { 61 | if key == envoy.GatewayDomain { 62 | for _, domain := range strings.Split(value, ",") { 63 | domain = strings.TrimSpace(domain) 64 | if domain != "" { 65 | up.Domains = appendDomain(up.Domains, domain) 66 | } 67 | } 68 | } 69 | if key == envoy.GatewayTimeout { 70 | d, err := time.ParseDuration(value) 71 | if err == nil { 72 | up.Timeout = d 73 | } 74 | } 75 | if key == envoy.GatewayRetries { 76 | r, err := strconv.Atoi(value) 77 | if err == nil { 78 | up.Retries = uint32(r) 79 | } 80 | } 81 | } 82 | return up 83 | } 84 | 85 | // IsValid checks if a virtual service service is eligible 86 | func (vsm *VirtualServiceManager) IsValid(vs appmeshv1.VirtualService) bool { 87 | if vs.Spec.VirtualRouter == nil || 88 | len(vs.Spec.VirtualRouter.Listeners) < 1 || 89 | vs.Spec.VirtualRouter.Listeners[0].PortMapping.Port < 1 { 90 | return false 91 | } 92 | 93 | for key, value := range vs.Annotations { 94 | if vsm.optIn && key == envoy.GatewayExpose && value != "true" { 95 | return false 96 | } 97 | if key == envoy.GatewayExpose && value == "false" { 98 | return false 99 | } 100 | } 101 | return true 102 | } 103 | 104 | // VirtualServiceFromUnstructured converts an unstructured object to a virtual service 105 | func (vsm *VirtualServiceManager) VirtualServiceFromUnstructured(obj *unstructured.Unstructured) (*appmeshv1.VirtualService, error) { 106 | b, _ := json.Marshal(&obj) 107 | var svc appmeshv1.VirtualService 108 | err := json.Unmarshal(b, &svc) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | return &svc, nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/envoy/annotations.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import "strconv" 4 | 5 | const ( 6 | // GatewayPrefix prefix annotation 7 | GatewayPrefix = "gateway.appmesh.k8s.aws/" 8 | // GatewayExpose expose boolean annotation 9 | GatewayExpose = GatewayPrefix + "expose" 10 | // GatewayDomain annotation with a comma separated list of public or internal domains 11 | GatewayDomain = GatewayPrefix + "domain" 12 | // GatewayTimeout max response duration annotation 13 | GatewayTimeout = GatewayPrefix + "timeout" 14 | // GatewayRetries number of retries annotation 15 | GatewayRetries = GatewayPrefix + "retries" 16 | // GatewayPrimary primary virtual service name annotation 17 | GatewayPrimary = GatewayPrefix + "primary" 18 | // GatewayCanary canary virtual service name annotation 19 | GatewayCanary = GatewayPrefix + "canary" 20 | // GatewayCanaryWeight traffic weight percentage annotation 21 | GatewayCanaryWeight = GatewayPrefix + "canary-weight" 22 | ) 23 | 24 | // CanaryFromAnnotations parses the annotations and returns a canary object 25 | func CanaryFromAnnotations(an map[string]string) *Canary { 26 | var primaryCluster string 27 | var canaryCluster string 28 | var canaryWeight int 29 | for key, value := range an { 30 | if key == GatewayPrimary { 31 | primaryCluster = value 32 | } 33 | if key == GatewayCanary { 34 | canaryCluster = value 35 | } 36 | if key == GatewayCanaryWeight { 37 | r, err := strconv.Atoi(value) 38 | if err == nil { 39 | canaryWeight = r 40 | } 41 | } 42 | } 43 | 44 | if primaryCluster != "" && canaryCluster != "" { 45 | return &Canary{ 46 | PrimaryCluster: primaryCluster, 47 | CanaryCluster: canaryCluster, 48 | CanaryWeight: canaryWeight, 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/envoy/cache.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "github.com/envoyproxy/go-control-plane/pkg/cache" 5 | "k8s.io/klog" 6 | ) 7 | 8 | // NewCache creates an Envoy cache 9 | func NewCache(ads bool) cache.SnapshotCache { 10 | return cache.NewSnapshotCache(ads, Hasher{}, log{}) 11 | } 12 | 13 | type log struct{} 14 | 15 | func (l log) Infof(format string, args ...interface{}) { 16 | klog.V(4).Infof(format, args...) 17 | } 18 | 19 | func (l log) Errorf(format string, args ...interface{}) { 20 | klog.Errorf(format, args...) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/envoy/cluster.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "time" 5 | 6 | envoyv2 "github.com/envoyproxy/go-control-plane/envoy/api/v2" 7 | cluster "github.com/envoyproxy/go-control-plane/envoy/api/v2/cluster" 8 | envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 9 | endpoint "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint" 10 | "github.com/golang/protobuf/ptypes" 11 | "github.com/golang/protobuf/ptypes/wrappers" 12 | ) 13 | 14 | func newCluster(upstream Upstream, timeout time.Duration) *envoyv2.Cluster { 15 | return &envoyv2.Cluster{ 16 | Name: upstream.Name, 17 | ConnectTimeout: ptypes.DurationProto(timeout), 18 | ClusterDiscoveryType: &envoyv2.Cluster_Type{Type: envoyv2.Cluster_STRICT_DNS}, 19 | DnsLookupFamily: envoyv2.Cluster_V4_ONLY, 20 | LbPolicy: envoyv2.Cluster_LEAST_REQUEST, 21 | LoadAssignment: &envoyv2.ClusterLoadAssignment{ 22 | ClusterName: upstream.Name, 23 | Endpoints: []*endpoint.LocalityLbEndpoints{{ 24 | LbEndpoints: []*endpoint.LbEndpoint{{ 25 | HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 26 | Endpoint: &endpoint.Endpoint{ 27 | Address: newAddress(upstream.Host, upstream.Port), 28 | }, 29 | }, 30 | }}, 31 | }}, 32 | }, 33 | CircuitBreakers: &cluster.CircuitBreakers{ 34 | Thresholds: []*cluster.CircuitBreakers_Thresholds{{ 35 | MaxRetries: &wrappers.UInt32Value{Value: uint32(1024)}, 36 | }}, 37 | }, 38 | } 39 | } 40 | 41 | func newAddress(address string, port uint32) *envoycore.Address { 42 | return &envoycore.Address{Address: &envoycore.Address_SocketAddress{ 43 | SocketAddress: &envoycore.SocketAddress{ 44 | Address: address, 45 | Protocol: envoycore.SocketAddress_TCP, 46 | PortSpecifier: &envoycore.SocketAddress_PortValue{ 47 | PortValue: port, 48 | }, 49 | }, 50 | }} 51 | } 52 | -------------------------------------------------------------------------------- /pkg/envoy/hasher.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 4 | 5 | // Hasher computes string identifiers for Envoy nodes. 6 | type Hasher struct{} 7 | 8 | // ID returns the Envoy node ID 9 | func (h Hasher) ID(node *envoycore.Node) string { 10 | if node == nil { 11 | return "envoy" 12 | } 13 | return node.Id 14 | } 15 | -------------------------------------------------------------------------------- /pkg/envoy/listener.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "time" 5 | 6 | envoyv2 "github.com/envoyproxy/go-control-plane/envoy/api/v2" 7 | listener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" 8 | route "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" 9 | hcm "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" 10 | "github.com/envoyproxy/go-control-plane/pkg/wellknown" 11 | "github.com/golang/protobuf/ptypes" 12 | "github.com/golang/protobuf/ptypes/wrappers" 13 | ) 14 | 15 | func newListener(name, address string, port uint32, cm *hcm.HttpConnectionManager) (*envoyv2.Listener, error) { 16 | cmAny, err := ptypes.MarshalAny(cm) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return &envoyv2.Listener{ 22 | Name: name, 23 | Address: newAddress(address, port), 24 | FilterChains: []*listener.FilterChain{{ 25 | Filters: []*listener.Filter{{ 26 | Name: wellknown.HTTPConnectionManager, 27 | ConfigType: &listener.Filter_TypedConfig{ 28 | TypedConfig: cmAny, 29 | }, 30 | }}, 31 | }}, 32 | }, nil 33 | } 34 | 35 | func newConnectionManager(routeName string, vhosts []*route.VirtualHost, drainTimeout time.Duration) *hcm.HttpConnectionManager { 36 | return &hcm.HttpConnectionManager{ 37 | CodecType: hcm.HttpConnectionManager_AUTO, 38 | DrainTimeout: ptypes.DurationProto(drainTimeout), 39 | StatPrefix: "ingress_http", 40 | RouteSpecifier: &hcm.HttpConnectionManager_RouteConfig{ 41 | RouteConfig: &envoyv2.RouteConfiguration{ 42 | Name: routeName, 43 | VirtualHosts: vhosts, 44 | ValidateClusters: &wrappers.BoolValue{Value: true}, 45 | }, 46 | }, 47 | HttpFilters: []*hcm.HttpFilter{{ 48 | Name: wellknown.Router, 49 | }}, 50 | UseRemoteAddress: &wrappers.BoolValue{Value: true}, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/envoy/snapshot.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | 9 | route "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" 10 | "github.com/envoyproxy/go-control-plane/pkg/cache" 11 | "github.com/mitchellh/hashstructure" 12 | "k8s.io/klog" 13 | ) 14 | 15 | // Snapshot manages Envoy clusters and listeners cache snapshots 16 | type Snapshot struct { 17 | version uint64 18 | cache cache.SnapshotCache 19 | upstreams *sync.Map 20 | checksum uint64 21 | nodeId string 22 | } 23 | 24 | // NewSnapshot creates an Envoy cache snapshot manager 25 | func NewSnapshot(cache cache.SnapshotCache) *Snapshot { 26 | return &Snapshot{ 27 | version: 0, 28 | cache: cache, 29 | upstreams: new(sync.Map), 30 | } 31 | } 32 | 33 | // Store inserts or updates an upstream in the in-memory cache 34 | func (s *Snapshot) Store(key string, value Upstream) { 35 | s.upstreams.Store(key, value) 36 | } 37 | 38 | // Delete removes an upstream from the in-memory cache 39 | func (s *Snapshot) Delete(key string) { 40 | s.upstreams.Delete(key) 41 | } 42 | 43 | // Len returns the number of upstreams stored in the in-memory cache 44 | func (s *Snapshot) Len() int { 45 | var length int 46 | s.upstreams.Range(func(_, _ interface{}) bool { 47 | length++ 48 | return true 49 | }) 50 | return length 51 | } 52 | 53 | func (s *Snapshot) getNodeId() (string, error) { 54 | if s.nodeId != "" { 55 | return s.nodeId, nil 56 | } 57 | if len(s.cache.GetStatusKeys()) < 1 { 58 | return "", fmt.Errorf("cache has no node IDs, status keys %d", len(s.cache.GetStatusKeys())) 59 | } 60 | return s.cache.GetStatusKeys()[0], nil 61 | } 62 | 63 | // Sync reconciles the in-memory cache of upstreams 64 | // with the Envoy cache by creating a new snapshot 65 | func (s *Snapshot) Sync() error { 66 | nodeId, err := s.getNodeId() 67 | if err != nil { 68 | return err 69 | } 70 | upstreams := make(map[string]Upstream) 71 | var listeners []cache.Resource 72 | var clusters []cache.Resource 73 | var vhosts []*route.VirtualHost 74 | 75 | s.upstreams.Range(func(key interface{}, value interface{}) bool { 76 | k := key.(string) 77 | upstream := value.(Upstream) 78 | upstreams[k] = upstream 79 | return true 80 | }) 81 | 82 | checksum, err := hashstructure.Hash(upstreams, nil) 83 | if err != nil { 84 | return fmt.Errorf("checksum error %v", err) 85 | } 86 | 87 | if checksum == s.checksum { 88 | return nil 89 | } 90 | 91 | for _, upstream := range upstreams { 92 | cluster := newCluster(upstream, time.Second) 93 | clusters = append(clusters, cluster) 94 | vh := newVirtualHost(upstream) 95 | vhosts = append(vhosts, &vh) 96 | } 97 | 98 | cm := newConnectionManager("local_route", vhosts, 5*time.Second) 99 | httpListener, err := newListener("listener_http", "0.0.0.0", 8080, cm) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | listeners = append(listeners, httpListener) 105 | 106 | atomic.AddUint64(&s.version, 1) 107 | snapshot := cache.NewSnapshot(fmt.Sprint(s.version), nil, clusters, nil, listeners) 108 | 109 | if err := snapshot.Consistent(); err != nil { 110 | return err 111 | } 112 | 113 | err = s.cache.SetSnapshot(nodeId, snapshot) 114 | if err != nil { 115 | return fmt.Errorf("error while setting snapshot %v", err) 116 | } 117 | 118 | atomic.StoreUint64(&s.checksum, checksum) 119 | klog.Infof("cache updated for %d services, version %d, checksum %d", len(upstreams), s.version, checksum) 120 | 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /pkg/envoy/snapshot_test.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func mockUpstream(i int, prefix string) (string, Upstream) { 10 | u := Upstream{ 11 | Name: fmt.Sprintf("app%d-test-9898", i), 12 | Host: fmt.Sprintf("app%d.test", i), 13 | Port: 9898, 14 | PortName: "http", 15 | Domains: []string{fmt.Sprintf("app%d.test.io", i)}, 16 | Prefix: prefix, 17 | Retries: 2, 18 | Timeout: 2 * time.Second, 19 | Canary: &Canary{ 20 | PrimaryCluster: fmt.Sprintf("app%d-primary-test-9898", i), 21 | CanaryCluster: fmt.Sprintf("app%d-canary-test-9898", i), 22 | CanaryWeight: 50, 23 | }, 24 | } 25 | return fmt.Sprintf("test/app%d", i), u 26 | } 27 | 28 | func mockUpstreams(prefix string) map[string]Upstream { 29 | m := make(map[string]Upstream) 30 | for i := 0; i < 10; i++ { 31 | k, u := mockUpstream(i, prefix) 32 | m[k] = u 33 | } 34 | return m 35 | } 36 | 37 | func TestSnapshot_Sync(t *testing.T) { 38 | cache := NewCache(true) 39 | snapshot := NewSnapshot(cache) 40 | snapshot.nodeId = "test" 41 | 42 | // test init 43 | i := 0 44 | for key, value := range mockUpstreams("/") { 45 | snapshot.Store(key, value) 46 | i++ 47 | if i == 5 { 48 | break 49 | } 50 | } 51 | 52 | err := snapshot.Sync() 53 | if err != nil { 54 | t.Fatal(err.Error()) 55 | } 56 | 57 | snap, err := snapshot.cache.GetSnapshot(snapshot.nodeId) 58 | if err != nil { 59 | t.Fatal(err.Error()) 60 | } 61 | 62 | if len(snap.Clusters.Items) != 5 { 63 | t.Errorf("Got clusters %v wanted %v", len(snap.Clusters.Items), 5) 64 | } 65 | 66 | if snap.Listeners.Version != "1" { 67 | t.Errorf("Got version %v wanted %v", snap.Listeners.Version, "1") 68 | } 69 | 70 | // test insert 71 | for key, value := range mockUpstreams("/") { 72 | snapshot.Store(key, value) 73 | } 74 | 75 | err = snapshot.Sync() 76 | if err != nil { 77 | t.Fatal(err.Error()) 78 | } 79 | 80 | snap, err = snapshot.cache.GetSnapshot(snapshot.nodeId) 81 | if err != nil { 82 | t.Fatal(err.Error()) 83 | } 84 | 85 | if len(snap.Clusters.Items) != 10 { 86 | t.Errorf("Got clusters %v wanted %v", len(snap.Clusters.Items), 10) 87 | } 88 | 89 | if snap.Listeners.Version != "2" { 90 | t.Errorf("Got version %v wanted %v", snap.Listeners.Version, "2") 91 | } 92 | 93 | // test update 94 | for i := 0; i < 2; i++ { 95 | k, u := mockUpstream(i, "/test") 96 | snapshot.Store(k, u) 97 | } 98 | 99 | err = snapshot.Sync() 100 | if err != nil { 101 | t.Fatal(err.Error()) 102 | } 103 | 104 | snap, err = snapshot.cache.GetSnapshot(snapshot.nodeId) 105 | if err != nil { 106 | t.Fatal(err.Error()) 107 | } 108 | 109 | if len(snap.Clusters.Items) != 10 { 110 | t.Errorf("Got clusters %v wanted %v", len(snap.Clusters.Items), 10) 111 | } 112 | 113 | if snap.Listeners.Version != "3" { 114 | t.Errorf("Got version %v wanted %v", snap.Listeners.Version, "3") 115 | } 116 | 117 | // test delete 118 | for i := 0; i < 10; i++ { 119 | snapshot.Delete(fmt.Sprintf("test/app%d", i)) 120 | } 121 | 122 | err = snapshot.Sync() 123 | if err != nil { 124 | t.Fatal(err.Error()) 125 | } 126 | 127 | snap, err = snapshot.cache.GetSnapshot(snapshot.nodeId) 128 | if err != nil { 129 | t.Fatal(err.Error()) 130 | } 131 | 132 | if len(snap.Clusters.Items) != 0 { 133 | t.Errorf("Got clusters %v wanted %v", len(snap.Clusters.Items), 0) 134 | } 135 | 136 | if snap.Listeners.Version != "4" { 137 | t.Errorf("Got version %v wanted %v", snap.Listeners.Version, "4") 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /pkg/envoy/upstream.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Upstream is a compact form of an Envoy cluster and virtual host 8 | type Upstream struct { 9 | Name string `json:"name"` 10 | Host string `json:"host"` 11 | Port uint32 `json:"port"` 12 | PortName string `json:"portName"` 13 | Domains []string `json:"domains"` 14 | Prefix string `json:"prefix"` 15 | Retries uint32 `json:"retries"` 16 | Timeout time.Duration `json:"timeout"` 17 | Canary *Canary `json:"canary"` 18 | } 19 | 20 | // Canary is a compact form of an Envoy weighted cluster 21 | type Canary struct { 22 | PrimaryCluster string `json:"primaryCluster"` 23 | CanaryCluster string `json:"canaryCluster"` 24 | CanaryWeight int `json:"canaryWeight"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/envoy/virtualhost.go: -------------------------------------------------------------------------------- 1 | package envoy 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 8 | route "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" 9 | "github.com/golang/protobuf/ptypes" 10 | "github.com/golang/protobuf/ptypes/wrappers" 11 | ) 12 | 13 | func newVirtualHost(upstream Upstream) route.VirtualHost { 14 | action := &route.RouteAction{ 15 | HostRewriteSpecifier: &route.RouteAction_HostRewrite{ 16 | HostRewrite: upstream.Host, 17 | }, 18 | ClusterSpecifier: &route.RouteAction_Cluster{ 19 | Cluster: upstream.Name, 20 | }, 21 | Timeout: ptypes.DurationProto(upstream.Timeout), 22 | } 23 | 24 | if upstream.Canary != nil && upstream.Canary.CanaryCluster != "" && upstream.Canary.PrimaryCluster != "" { 25 | action = &route.RouteAction{ 26 | HostRewriteSpecifier: &route.RouteAction_HostRewrite{ 27 | HostRewrite: upstream.Host, 28 | }, 29 | ClusterSpecifier: &route.RouteAction_WeightedClusters{ 30 | WeightedClusters: &route.WeightedCluster{ 31 | Clusters: []*route.WeightedCluster_ClusterWeight{ 32 | { 33 | Name: upstream.Canary.CanaryCluster, 34 | Weight: &wrappers.UInt32Value{Value: uint32(upstream.Canary.CanaryWeight)}, 35 | }, 36 | { 37 | Name: upstream.Canary.PrimaryCluster, 38 | Weight: &wrappers.UInt32Value{Value: uint32(100 - upstream.Canary.CanaryWeight)}, 39 | }, 40 | }, 41 | }, 42 | }, 43 | Timeout: ptypes.DurationProto(upstream.Timeout), 44 | } 45 | } 46 | 47 | r := &route.Route{ 48 | Match: &route.RouteMatch{ 49 | PathSpecifier: &route.RouteMatch_Prefix{ 50 | Prefix: upstream.Prefix, 51 | }, 52 | }, 53 | Action: &route.Route_Route{ 54 | Route: action, 55 | }, 56 | } 57 | 58 | return route.VirtualHost{ 59 | Name: upstream.Name, 60 | Domains: upstream.Domains, 61 | Routes: []*route.Route{r}, 62 | RetryPolicy: makeRetryPolicy(upstream.Retries, upstream.Timeout), 63 | RequestHeadersToAdd: []*envoycore.HeaderValueOption{ 64 | { 65 | Header: &envoycore.HeaderValue{ 66 | Key: "l5d-dst-override", 67 | Value: fmt.Sprintf("%s.svc.cluster.local:%d", upstream.Host, upstream.Port), 68 | }, 69 | }, 70 | }, 71 | RequestHeadersToRemove: []string{"l5d-remote-ip", "l5d-server-id"}, 72 | } 73 | } 74 | 75 | func makeRetryPolicy(retries uint32, timeout time.Duration) *route.RetryPolicy { 76 | return &route.RetryPolicy{ 77 | RetryOn: "connect-failure,refused-stream,unavailable,cancelled,resource-exhausted,retriable-status-codes", 78 | PerTryTimeout: ptypes.DurationProto(timeout), 79 | NumRetries: &wrappers.UInt32Value{Value: retries}, 80 | HostSelectionRetryMaxAttempts: 5, 81 | RetriableStatusCodes: []uint32{503}, 82 | RetryHostPredicate: []*route.RetryPolicy_RetryHostPredicate{ 83 | {Name: "envoy.retry_host_predicates.previous_hosts"}, 84 | }, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/server/callbacks.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2" 8 | "k8s.io/klog" 9 | ) 10 | 11 | type callbacks struct { 12 | signal chan struct{} 13 | fetches int 14 | requests int 15 | mu sync.Mutex 16 | } 17 | 18 | func (cb *callbacks) Report() { 19 | cb.mu.Lock() 20 | defer cb.mu.Unlock() 21 | klog.V(4).Infof("report requests %v", cb.requests) 22 | } 23 | 24 | func (cb *callbacks) OnStreamOpen(_ context.Context, id int64, typ string) error { 25 | klog.V(4).Infof("stream %d open for %s", id, typ) 26 | return nil 27 | } 28 | 29 | func (cb *callbacks) OnStreamClosed(id int64) { 30 | klog.V(4).Infof("stream %d closed", id) 31 | } 32 | 33 | func (cb *callbacks) OnStreamRequest(int64, *v2.DiscoveryRequest) error { 34 | cb.mu.Lock() 35 | defer cb.mu.Unlock() 36 | cb.requests++ 37 | if cb.signal != nil { 38 | close(cb.signal) 39 | cb.signal = nil 40 | } 41 | return nil 42 | } 43 | 44 | func (cb *callbacks) OnStreamResponse(int64, *v2.DiscoveryRequest, *v2.DiscoveryResponse) { 45 | cb.Report() 46 | } 47 | 48 | func (cb *callbacks) OnFetchRequest(_ context.Context, req *v2.DiscoveryRequest) error { 49 | cb.mu.Lock() 50 | defer cb.mu.Unlock() 51 | cb.fetches++ 52 | if cb.signal != nil { 53 | close(cb.signal) 54 | cb.signal = nil 55 | } 56 | return nil 57 | } 58 | 59 | func (cb *callbacks) OnFetchResponse(*v2.DiscoveryRequest, *v2.DiscoveryResponse) {} 60 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2" 9 | discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2" 10 | "github.com/envoyproxy/go-control-plane/pkg/cache" 11 | xds "github.com/envoyproxy/go-control-plane/pkg/server" 12 | "google.golang.org/grpc" 13 | "k8s.io/klog" 14 | ) 15 | 16 | // Server Envoy management server 17 | type Server struct { 18 | config cache.SnapshotCache 19 | xdsServer xds.Server 20 | port int 21 | cb *callbacks 22 | cbSignal chan struct{} 23 | } 24 | 25 | // NewServer creates an Envoy xDS management server 26 | func NewServer(port int, config cache.SnapshotCache) *Server { 27 | cbSignal := make(chan struct{}) 28 | cb := &callbacks{ 29 | signal: cbSignal, 30 | fetches: 0, 31 | requests: 0, 32 | } 33 | 34 | xdsServer := xds.NewServer(config, cb) 35 | 36 | return &Server{ 37 | config: config, 38 | xdsServer: xdsServer, 39 | port: port, 40 | cbSignal: cbSignal, 41 | cb: cb, 42 | } 43 | } 44 | 45 | // Serve starts the Envoy xDS management server 46 | func (srv *Server) Serve(ctx context.Context) { 47 | var options []grpc.ServerOption 48 | options = append(options, grpc.MaxConcurrentStreams(1000000)) 49 | grpcServer := grpc.NewServer(options...) 50 | 51 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", srv.port)) 52 | if err != nil { 53 | klog.Fatalf("server failed to listen on %d %v", srv.port, err) 54 | } 55 | 56 | discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, srv.xdsServer) 57 | v2.RegisterEndpointDiscoveryServiceServer(grpcServer, srv.xdsServer) 58 | v2.RegisterClusterDiscoveryServiceServer(grpcServer, srv.xdsServer) 59 | v2.RegisterRouteDiscoveryServiceServer(grpcServer, srv.xdsServer) 60 | v2.RegisterListenerDiscoveryServiceServer(grpcServer, srv.xdsServer) 61 | 62 | go func() { 63 | if err = grpcServer.Serve(listener); err != nil { 64 | klog.Error(err) 65 | } 66 | }() 67 | <-ctx.Done() 68 | 69 | grpcServer.GracefulStop() 70 | } 71 | 72 | // Report waits for Envoy to access the xDS server 73 | // and logs the number of gRPC requests 74 | func (srv *Server) Report() { 75 | <-srv.cbSignal 76 | srv.cb.Report() 77 | } 78 | -------------------------------------------------------------------------------- /pkg/signals/signal.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package signals 15 | 16 | import ( 17 | "os" 18 | "os/signal" 19 | ) 20 | 21 | var onlyOneSignalHandler = make(chan struct{}) 22 | 23 | // SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned 24 | // which is closed on one of these signals. If a second signal is caught, the program 25 | // is terminated with exit code 1. 26 | func SetupSignalHandler() (stopCh <-chan struct{}) { 27 | close(onlyOneSignalHandler) // panics when called twice 28 | 29 | stop := make(chan struct{}) 30 | c := make(chan os.Signal, 2) 31 | signal.Notify(c, shutdownSignals...) 32 | go func() { 33 | <-c 34 | close(stop) 35 | <-c 36 | os.Exit(1) // second signal. Exit directly. 37 | }() 38 | 39 | return stop 40 | } 41 | -------------------------------------------------------------------------------- /pkg/signals/signal_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | /* 4 | Copyright 2017 The Kubernetes Authors. 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package signals 17 | 18 | import ( 19 | "os" 20 | "syscall" 21 | ) 22 | 23 | var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} 24 | -------------------------------------------------------------------------------- /pkg/signals/signal_windows.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package signals 15 | 16 | import ( 17 | "os" 18 | ) 19 | 20 | var shutdownSignals = []os.Signal{os.Interrupt} 21 | -------------------------------------------------------------------------------- /test/e2e-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Build and load container image in Kubernetes Kind 4 | 5 | set -o errexit 6 | 7 | function main() { 8 | docker build -t test/flagger-appmesh-gateway:latest . 9 | kind load docker-image test/flagger-appmesh-gateway:latest 10 | } 11 | 12 | main 13 | -------------------------------------------------------------------------------- /test/e2e-lib.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # App Mesh Gateway e2e testing helpers 4 | 5 | set -o errexit 6 | 7 | function infof() { 8 | echo -e "\e[32m${1}\e[0m" >&3 9 | } 10 | 11 | function errorf() { 12 | echo -e "\e[31m${1}\e[0m" >&3 13 | exit 1 14 | } 15 | 16 | function waitForDeployment() { 17 | infof "Testing deployment $1" 18 | retries=10 19 | count=0 20 | ok=false 21 | until $ok; do 22 | kubectl -n $2 get deployment/$1 && ok=true || ok=false 23 | sleep 6 24 | count=$(($count + 1)) 25 | if [[ $count -eq $retries ]]; then 26 | errorf "No more retries left" 27 | fi 28 | done 29 | 30 | kubectl -n $2 rollout status deployment/$1 --timeout=1m 31 | infof "✔ deployment/$1 test passed" 32 | } 33 | 34 | function waitForService() { 35 | infof "Testing service $1" 36 | retries=10 37 | count=0 38 | ok=false 39 | until $ok; do 40 | kubectl -n $2 get svc/$1 && ok=true || ok=false 41 | sleep 6 42 | count=$(($count + 1)) 43 | if [[ $count -eq $retries ]]; then 44 | errorf "No more retries left" 45 | fi 46 | done 47 | infof "✔ service/$1 test passed" 48 | } 49 | 50 | function waitForMesh() { 51 | infof "Waiting for mesh $1" 52 | retries=10 53 | count=0 54 | ok=false 55 | until $ok; do 56 | kubectl -n $2 get mesh/$1 && ok=true || ok=false 57 | sleep 6 58 | count=$(($count + 1)) 59 | if [[ $count -eq $retries ]]; then 60 | errorf "No more retries left" 61 | fi 62 | done 63 | infof "✔ mesh/$1 installed" 64 | } 65 | 66 | function waitForVirtualNode() { 67 | infof "Testing virtual node $1" 68 | retries=10 69 | count=0 70 | ok=false 71 | until $ok; do 72 | kubectl -n $2 get virtualnode/$1 && ok=true || ok=false 73 | sleep 6 74 | count=$(($count + 1)) 75 | if [[ $count -eq $retries ]]; then 76 | errorf "No more retries left" 77 | fi 78 | done 79 | infof "✔ virtualnode/$1 test passed" 80 | } 81 | 82 | function waitForVirtualNodeBackend() { 83 | infof "Testing virtual node backend $1" 84 | retries=10 85 | count=0 86 | ok=false 87 | until $ok; do 88 | kubectl -n $2 get virtualnode/$1 -oyaml | grep $3 && ok=true || ok=false 89 | sleep 6 90 | count=$(($count + 1)) 91 | if [[ $count -eq $retries ]]; then 92 | errorf "No more retries left" 93 | fi 94 | done 95 | infof "✔ virtualnode/$1 backend $3 test passed" 96 | } 97 | 98 | function waitForVirtualService() { 99 | infof "Testing virtual service $1" 100 | retries=10 101 | count=0 102 | ok=false 103 | until $ok; do 104 | kubectl -n $2 get virtualservice/$1 && ok=true || ok=false 105 | sleep 6 106 | count=$(($count + 1)) 107 | if [[ $count -eq $retries ]]; then 108 | errorf "No more retries left" 109 | fi 110 | done 111 | infof "✔ virtualservice/$1 test passed" 112 | } 113 | 114 | function applyCRDs() { 115 | kubectl apply -k github.com/aws/eks-charts/stable/appmesh-controller//crds?ref=master 116 | } 117 | 118 | function applyMesh() { 119 | cat <