├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── charts ├── k8s-cloudwatch-adapter-crd │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── crd.yaml │ │ └── rbac.yaml │ └── values.yaml └── k8s-cloudwatch-adapter │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── _helpers.tpl │ ├── apiservice.yaml │ ├── deployment.yaml │ ├── rbac.yaml │ ├── service.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── cmd └── adapter │ └── adapter.go ├── deploy ├── Dockerfile ├── adapter.yaml └── crd.yaml ├── docs ├── cross-account.md └── schema.md ├── go.mod ├── go.sum ├── hack ├── custom-boilerplate.go.txt ├── gen-deploy.sh ├── run-tests.sh ├── tools.go ├── update-codegen.sh ├── verify-codegen.sh └── verify-deploy.sh ├── pkg ├── apis │ └── metrics │ │ ├── register.go │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── externalmetric.go │ │ ├── register.go │ │ └── zz_generated.deepcopy.go ├── aws │ ├── client.go │ ├── interface.go │ ├── util.go │ └── util_test.go ├── client │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── metrics │ │ │ └── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── externalmetric.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_externalmetric.go │ │ │ └── fake_metrics_client.go │ │ │ ├── generated_expansion.go │ │ │ └── metrics_client.go │ ├── informers │ │ └── externalversions │ │ │ ├── factory.go │ │ │ ├── generic.go │ │ │ ├── internalinterfaces │ │ │ └── factory_interfaces.go │ │ │ └── metrics │ │ │ ├── interface.go │ │ │ └── v1alpha1 │ │ │ ├── externalmetric.go │ │ │ └── interface.go │ └── listers │ │ └── metrics │ │ └── v1alpha1 │ │ ├── expansion_generated.go │ │ └── externalmetric.go ├── controller │ ├── controller.go │ ├── controller_test.go │ ├── handler.go │ └── handler_test.go ├── metriccache │ └── metric_cache.go └── provider │ ├── provider.go │ └── provider_external.go └── samples └── sqs ├── Makefile ├── README.md ├── consumer ├── Dockerfile └── main.go ├── deploy ├── consumer-deployment.yaml ├── externalmetric.yaml ├── hpa.yaml └── producer-deployment.yaml └── producer ├── Dockerfile └── main.go /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | _output/ 3 | adapter 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | dist: bionic 4 | 5 | env: 6 | - DOCKER_CLI_EXPERIMENTAL=enabled 7 | 8 | go: 9 | - "1.14.x" 10 | 11 | # Only clone the most recent commit. 12 | git: 13 | depth: 1 14 | 15 | sudo: required 16 | services: 17 | - docker 18 | 19 | before_install: 20 | - sudo rm -rf /var/lib/apt/lists/* 21 | - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 22 | - lsb_release -cs 23 | - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) edge" 24 | - sudo apt-get update 25 | - sudo apt purge --auto-remove qemu-user qemu-user-binfmt binfmt-support 26 | - sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce qemu-user-static 27 | - mkdir -vp ~/.docker/cli-plugins/ 28 | - curl --silent -L "https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-amd64" > ~/.docker/cli-plugins/docker-buildx 29 | - chmod a+x ~/.docker/cli-plugins/docker-buildx 30 | - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 31 | 32 | script: 33 | - docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" 34 | - docker version 35 | - docker buildx version 36 | - docker buildx ls 37 | - docker buildx create --name builder --driver docker-container --use 38 | - docker buildx inspect --bootstrap 39 | - 'if [ "$TRAVIS_PULL_REQUEST" != false ]; then make docker; else travis_wait make docker-multiarch TRAVIS_TAG=$TRAVIS_TAG; fi' -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/awslabs/cloudwatch-adapter-for-k8s/issues), or [recently closed](https://github.com/awslabs/cloudwatch-adapter-for-k8s/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/cloudwatch-adapter-for-k8s/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/awslabs/cloudwatch-adapter-for-k8s/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14-alpine as builder 2 | RUN apk add -U --no-cache ca-certificates 3 | WORKDIR ${GOPATH}/src/github.com/awslabs/k8s-cloudwatch-adapter 4 | COPY . ./ 5 | RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -tags=netgo -o /adapter cmd/adapter/adapter.go 6 | 7 | FROM busybox 8 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 9 | COPY --from=builder /adapter /adapter 10 | USER 1001:1001 11 | ENTRYPOINT ["/adapter", "--logtostderr=true"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REGISTRY?=chankh 2 | IMAGE?=k8s-cloudwatch-adapter 3 | TEMP_DIR:=$(shell mktemp -d /tmp/$(IMAGE).XXXXXX) 4 | ARCH?=amd64 5 | PLATFORMS=linux/arm64/v8,linux/amd64 6 | OUT_DIR?=./_output 7 | VENDOR_DOCKERIZED?=0 8 | GIT_HASH?=$(shell git rev-parse --short HEAD) 9 | TAG:=$(or ${TRAVIS_TAG},${TRAVIS_TAG},latest) 10 | GOIMAGE=golang:1.14 11 | GOFLAGS=-mod=vendor -tags=netgo 12 | SRC:=$(shell find pkg cmd -type f -name "*.go") 13 | 14 | .PHONY: all docker-build build docker docker-multiarch push test 15 | 16 | all: verify-apis test $(OUT_DIR)/$(ARCH)/adapter 17 | 18 | $(OUT_DIR)/%/adapter: $(SRC) 19 | CGO_ENABLED=0 GOARCH=$* go build $(GOFLAGS) -o $(OUT_DIR)/$*/adapter cmd/adapter/adapter.go 20 | 21 | docker-build: verify-apis test 22 | cp deploy/Dockerfile $(TEMP_DIR)/Dockerfile 23 | 24 | docker run --rm -v $(TEMP_DIR):/build -v $(shell pwd):/go/src/github.com/awslabs/k8s-cloudwatch-adapter -e GOARCH=$(ARCH) -e GOFLAGS="$(GOFLAGS)" -w /go/src/github.com/awslabs/k8s-cloudwatch-adapter $(GOIMAGE) /bin/bash -c "\ 25 | CGO_ENABLED=0 GO111MODULE=on go build -o /build/adapter cmd/adapter/adapter.go" 26 | 27 | docker build -t $(REGISTRY)/$(IMAGE):$(VERSION) $(TEMP_DIR) 28 | rm -rf $(TEMP_DIR) 29 | 30 | build: $(OUT_DIR)/$(ARCH)/adapter 31 | 32 | docker: verify-apis test 33 | docker build --pull -t $(REGISTRY)/$(IMAGE):$(TAG) . 34 | 35 | docker-multiarch: verify-apis test 36 | docker buildx build --pull --push --platform $(PLATFORMS) --tag $(REGISTRY)/$(IMAGE):$(TAG) . 37 | 38 | push: docker 39 | docker push $(REGISTRY)/$(IMAGE):$(TAG) 40 | 41 | vendor: go.mod 42 | ifeq ($(VENDOR_DOCKERIZED),1) 43 | docker run -it -v $(shell pwd):/src/k8s-cloudwatch-adapter -w /src/k8s-cloudwatch-adapter $(GOIMAGE) /bin/bash -c "\ 44 | go mod vendor" 45 | else 46 | go mod vendor 47 | endif 48 | 49 | test: 50 | CGO_ENABLED=0 GO111MODULE=on go test -cover ./pkg/... 51 | 52 | clean: 53 | rm -rf ${OUT_DIR} vendor 54 | 55 | # Code gen helpers 56 | gen-apis: vendor 57 | hack/update-codegen.sh 58 | 59 | verify-apis: vendor 60 | hack/verify-codegen.sh 61 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Amazon Cloudwatch Adapter for Kubernetes 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/awslabs/k8s-cloudwatch-adapter.svg?branch=master)](https://travis-ci.org/awslabs/k8s-cloudwatch-adapter) 2 | [![GitHub 3 | release](https://img.shields.io/github/release/awslabs/k8s-cloudwatch-adapter/all.svg)](https://github.com/awslabs/k8s-cloudwatch-adapter/releases) 4 | [![docker image 5 | size](https://shields.beevelop.com/docker/image/image-size/chankh/k8s-cloudwatch-adapter/latest.svg)](https://hub.docker.com/r/chankh/k8s-cloudwatch-adapter) 6 | [![image 7 | layers](https://shields.beevelop.com/docker/image/layers/chankh/k8s-cloudwatch-adapter/latest.svg)](https://hub.docker.com/r/chankh/k8s-cloudwatch-adapter) 8 | [![image 9 | pulls](https://shields.beevelop.com/docker/pulls/chankh/k8s-cloudwatch-adapter.svg)](https://hub.docker.com/r/chankh/k8s-cloudwatch-adapter) 10 | 11 | > Attention! This project has been archived and is no longer being worked on. If you are looking for a metrics server that can consume metrics from CloudWatch, please consider using the [KEDA](https://keda.sh) project instead. KEDA is a Kubernetes-based Event Driven Autoscaler. With KEDA, you can drive the scaling of any container in Kubernetes based on the number of events needing to be processed. For an overview of KEDA, see [An overview of Kubernetes Event-Driven Autoscaling](https://youtu.be/H5eZEq_wqSE). 12 | 13 | # Kubernetes Custom Metrics Adapter for Kubernetes 14 | 15 | 16 | An implementation of the Kubernetes [Custom Metrics API and External Metrics 17 | API](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-metrics-apis) 18 | for AWS CloudWatch metrics. 19 | 20 | This adapter allows you to scale your Kubernetes deployment using the [Horizontal Pod 21 | Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) (HPA) with 22 | metrics from AWS CloudWatch. 23 | 24 | ## Prerequisites 25 | This adapter requires the following permissions to access metric data from Amazon CloudWatch. 26 | - cloudwatch:GetMetricData 27 | 28 | You can create an IAM policy using this template, and attach it to the [Service Account Role](https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html) if you are using 29 | [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). 30 | 31 | ```json 32 | { 33 | "Version": "2012-10-17", 34 | "Statement": [ 35 | { 36 | "Effect": "Allow", 37 | "Action": [ 38 | "cloudwatch:GetMetricData" 39 | ], 40 | "Resource": "*" 41 | } 42 | ] 43 | } 44 | ``` 45 | 46 | ## Deploy 47 | Requires a Kubernetes cluster with Metric Server deployed, Amazon EKS cluster is fine too. 48 | 49 | Now deploy the adapter to your Kubernetes cluster: 50 | 51 | ```bash 52 | $ kubectl apply -f https://raw.githubusercontent.com/awslabs/k8s-cloudwatch-adapter/master/deploy/adapter.yaml 53 | namespace/custom-metrics created 54 | clusterrolebinding.rbac.authorization.k8s.io/k8s-cloudwatch-adapter:system:auth-delegator created 55 | rolebinding.rbac.authorization.k8s.io/k8s-cloudwatch-adapter-auth-reader created 56 | deployment.apps/k8s-cloudwatch-adapter created 57 | clusterrolebinding.rbac.authorization.k8s.io/k8s-cloudwatch-adapter-resource-reader created 58 | serviceaccount/k8s-cloudwatch-adapter created 59 | service/k8s-cloudwatch-adapter created 60 | apiservice.apiregistration.k8s.io/v1beta1.external.metrics.k8s.io created 61 | clusterrole.rbac.authorization.k8s.io/k8s-cloudwatch-adapter:external-metrics-reader created 62 | clusterrole.rbac.authorization.k8s.io/k8s-cloudwatch-adapter-resource-reader created 63 | clusterrolebinding.rbac.authorization.k8s.io/k8s-cloudwatch-adapter:external-metrics-reader created 64 | customresourcedefinition.apiextensions.k8s.io/externalmetrics.metrics.aws created 65 | clusterrole.rbac.authorization.k8s.io/k8s-cloudwatch-adapter:crd-metrics-reader created 66 | clusterrolebinding.rbac.authorization.k8s.io/k8s-cloudwatch-adapter:crd-metrics-reader created 67 | ``` 68 | 69 | This creates a new namespace `custom-metrics` and deploys the necessary ClusterRole, Service Account, 70 | Role Binding, along with the deployment of the adapter. 71 | 72 | Alternatively the crd and adapter can be deployed using the Helm chart in the `/charts` directory: 73 | 74 | ```bash 75 | $ helm install k8s-cloudwatch-adapter-crd ./charts/k8s-cloudwatch-adapter-crd 76 | NAME: k8s-cloudwatch-adapter-crd 77 | LAST DEPLOYED: Thu Sep 17 11:36:53 2020 78 | NAMESPACE: default 79 | STATUS: deployed 80 | REVISION: 1 81 | TEST SUITE: None 82 | $ helm install k8s-cloudwatch-adapter ./charts/k8s-cloudwatch-adapter \ 83 | > --namespace custom-metrics \ 84 | > --create-namespace 85 | NAME: k8s-cloudwatch-adapter 86 | LAST DEPLOYED: Fri Aug 14 13:20:17 2020 87 | NAMESPACE: custom-metrics 88 | STATUS: deployed 89 | REVISION: 1 90 | TEST SUITE: None 91 | ``` 92 | 93 | ### Verifying the deployment 94 | Next you can query the APIs to see if the adapter is deployed correctly by running: 95 | 96 | ```bash 97 | $ kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq . 98 | { 99 | "kind": "APIResourceList", 100 | "apiVersion": "v1", 101 | "groupVersion": "external.metrics.k8s.io/v1beta1", 102 | "resources": [ 103 | ] 104 | } 105 | ``` 106 | 107 | ## Deploying the sample application 108 | There is a sample SQS application provided in this repository for you to test how the adapter works. 109 | Refer to this [guide](samples/sqs/README.md). 110 | 111 | ## More docs 112 | - [Configuring cross account metric example](docs/cross-account.md) 113 | - [ExternalMetric CRD schema](docs/schema.md) 114 | 115 | ## License 116 | 117 | This library is licensed under the Apache 2.0 License. 118 | 119 | ## Issues 120 | Report any issues in the [Github Issues](https://github.com/awslabs/k8s-cloudwatch-adapter/issues) 121 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter-crd/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter-crd/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: k8s-cloudwatch-adapter-crd 3 | description: Helm chart for Kubernetes metrics adapter crd for Amazon CloudWatch 4 | type: application 5 | version: 0.1.1 6 | appVersion: 0.10.0 7 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter-crd/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{/* 4 | Expand the name of the chart. 5 | */}} 6 | {{- define "k8s-cloudwatch-adapter-crd.name" -}} 7 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 8 | {{- end -}} 9 | 10 | 11 | {{/* 12 | Create chart name and version as used by the chart label. 13 | */}} 14 | {{- define "k8s-cloudwatch-adapter-crd.chart" -}} 15 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | 18 | {{/* 19 | Create a default fully qualified app name. 20 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 21 | */}} 22 | {{- define "k8s-cloudwatch-adapter-crd.fullname" -}} 23 | {{- if .Values.fullnameOverride -}} 24 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 25 | {{- else -}} 26 | {{- $name := default .Chart.Name .Values.nameOverride -}} 27 | {{- if contains $name .Release.Name -}} 28 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 29 | {{- else -}} 30 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 31 | {{- end -}} 32 | {{- end -}} 33 | {{- end -}} 34 | 35 | {{/* 36 | Common labels 37 | */}} 38 | {{- define "k8s-cloudwatch-adapter-crd.labels" -}} 39 | helm.sh/chart: {{ include "k8s-cloudwatch-adapter-crd.chart" . }} 40 | {{ include "k8s-cloudwatch-adapter-crd.selectorLabels" . }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end -}} 46 | 47 | {{/* 48 | Selector labels 49 | */}} 50 | {{- define "k8s-cloudwatch-adapter-crd.selectorLabels" -}} 51 | app.kubernetes.io/name: {{ include "k8s-cloudwatch-adapter-crd.name" . }} 52 | app.kubernetes.io/instance: {{ .Release.Name }} 53 | app.kubernetes.io/component: crd 54 | {{- end -}} 55 | 56 | {{/* 57 | Create the name of the adapter service account to use 58 | */}} 59 | {{- define "k8s-cloudwatch-adapter-crd.serviceAccountName" -}} 60 | {{ default "k8s-cloudwatch-adapter" .Values.serviceAccount.name }} 61 | {{- end -}} 62 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter-crd/templates/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: externalmetrics.metrics.aws 5 | labels: 6 | {{- include "k8s-cloudwatch-adapter-crd.labels" . | nindent 4 }} 7 | spec: 8 | group: metrics.aws 9 | version: v1alpha1 10 | names: 11 | kind: ExternalMetric 12 | plural: externalmetrics 13 | singular: externalmetric 14 | shortNames: 15 | - em 16 | scope: Namespaced 17 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter-crd/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "k8s-cloudwatch-adapter-crd.fullname" . }}:crd-metrics-reader 5 | labels: 6 | {{- include "k8s-cloudwatch-adapter-crd.labels" . | nindent 4 }} 7 | rules: 8 | - apiGroups: 9 | - metrics.aws 10 | resources: 11 | - "externalmetrics" 12 | verbs: 13 | - list 14 | - get 15 | - watch 16 | --- 17 | apiVersion: rbac.authorization.k8s.io/v1 18 | kind: ClusterRoleBinding 19 | metadata: 20 | name: {{ include "k8s-cloudwatch-adapter-crd.fullname" . }}:crd-metrics-reader 21 | labels: 22 | {{- include "k8s-cloudwatch-adapter-crd.labels" . | nindent 4 }} 23 | roleRef: 24 | apiGroup: rbac.authorization.k8s.io 25 | kind: ClusterRole 26 | name: {{ include "k8s-cloudwatch-adapter-crd.fullname" . }}:crd-metrics-reader 27 | subjects: 28 | - name: {{ template "k8s-cloudwatch-adapter-crd.serviceAccountName" . }} 29 | kind: ServiceAccount 30 | namespace: {{ .Release.Namespace }} 31 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter-crd/values.yaml: -------------------------------------------------------------------------------- 1 | serviceAccount: 2 | # defaults to k8s-cloudwatch-adapter 3 | name: 4 | 5 | ## Labels to be added to the adapter Deployment 6 | ## 7 | labels: {} 8 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: k8s-cloudwatch-adapter 3 | description: Helm chart for Kubernetes metrics adapter for Amazon CloudWatch 4 | type: application 5 | version: 0.2.1 6 | appVersion: 0.10.0 7 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{/* 4 | Expand the name of the chart. 5 | */}} 6 | {{- define "k8s-cloudwatch-adapter.name" -}} 7 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 8 | {{- end -}} 9 | 10 | 11 | {{/* 12 | Create chart name and version as used by the chart label. 13 | */}} 14 | {{- define "k8s-cloudwatch-adapter.chart" -}} 15 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | 18 | {{/* 19 | Create a default fully qualified app name. 20 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 21 | */}} 22 | {{- define "k8s-cloudwatch-adapter.fullname" -}} 23 | {{- if .Values.fullnameOverride -}} 24 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 25 | {{- else -}} 26 | {{- $name := default .Chart.Name .Values.nameOverride -}} 27 | {{- if contains $name .Release.Name -}} 28 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 29 | {{- else -}} 30 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 31 | {{- end -}} 32 | {{- end -}} 33 | {{- end -}} 34 | 35 | {{/* 36 | Common labels 37 | */}} 38 | {{- define "k8s-cloudwatch-adapter.labels" -}} 39 | helm.sh/chart: {{ include "k8s-cloudwatch-adapter.chart" . }} 40 | {{ include "k8s-cloudwatch-adapter.selectorLabels" . }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end -}} 46 | 47 | {{/* 48 | Selector labels 49 | */}} 50 | {{- define "k8s-cloudwatch-adapter.selectorLabels" -}} 51 | app.kubernetes.io/name: {{ include "k8s-cloudwatch-adapter.name" . }} 52 | app.kubernetes.io/instance: {{ .Release.Name }} 53 | app.kubernetes.io/component: adapter 54 | {{- end -}} 55 | 56 | {{/* 57 | Create the name of the adapter service account to use 58 | */}} 59 | {{- define "k8s-cloudwatch-adapter.serviceAccountName" -}} 60 | {{- if .Values.serviceAccount.create -}} 61 | {{ default (include "k8s-cloudwatch-adapter.fullname" .) .Values.serviceAccount.name }} 62 | {{- else -}} 63 | {{ default "default" .Values.serviceAccount.name }} 64 | {{- end -}} 65 | {{- end -}} 66 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/templates/apiservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiregistration.k8s.io/v1beta1 2 | kind: APIService 3 | metadata: 4 | name: v1beta1.external.metrics.k8s.io 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 8 | spec: 9 | service: 10 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }} 11 | namespace: {{ .Release.Namespace }} 12 | group: external.metrics.k8s.io 13 | version: v1beta1 14 | insecureSkipTLSVerify: true 15 | groupPriorityMinimum: 100 16 | versionPriority: 100 17 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 6 | {{- with .Values.labels }} 7 | {{- toYaml . | nindent 4 }} 8 | {{- end }} 9 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }} 10 | {{- if .Values.annotations }} 11 | annotations: {{ toYaml .Values.annotations | nindent 4 }} 12 | {{- end }} 13 | spec: 14 | replicas: {{ .Values.replicaCount }} 15 | selector: 16 | matchLabels: 17 | {{- include "k8s-cloudwatch-adapter.selectorLabels" . | nindent 6 }} 18 | template: 19 | metadata: 20 | {{- if .Values.podAnnotations }} 21 | annotations: {{ toYaml .Values.podAnnotations | nindent 8 }} 22 | {{- end }} 23 | labels: 24 | {{- include "k8s-cloudwatch-adapter.selectorLabels" . | nindent 8 }} 25 | {{- if .Values.podLabels }} 26 | {{- toYaml .Values.podLabels | nindent 8 }} 27 | {{- end }} 28 | spec: 29 | serviceAccountName: {{ template "k8s-cloudwatch-adapter.serviceAccountName" . }} 30 | securityContext: 31 | fsGroup: 65534 32 | {{- if .Values.imagePullSecrets }} 33 | imagePullSecrets: {{ toYaml .Values.imagePullSecrets | nindent 8 }} 34 | {{- end }} 35 | {{- if .Values.priorityClassName }} 36 | priorityClassName: {{ .Values.priorityClassName }} 37 | {{- end }} 38 | containers: 39 | - name: {{ include "k8s-cloudwatch-adapter.fullname" . }} 40 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 41 | imagePullPolicy: "{{ .Values.image.pullPolicy }}" 42 | args: 43 | - /adapter 44 | {{- range $key, $val := .Values.args }} 45 | - --{{ $key }}={{ $val }} 46 | {{- end }} 47 | ports: 48 | - containerPort: 6443 49 | name: https 50 | - containerPort: 8080 51 | name: http 52 | volumeMounts: 53 | - mountPath: /tmp 54 | name: temp-vol 55 | resources: 56 | {{ toYaml .Values.resources | indent 10 }} 57 | volumes: 58 | - name: temp-vol 59 | emptyDir: {} 60 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 6 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}:system:auth-delegator 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: ClusterRole 10 | name: system:auth-delegator 11 | subjects: 12 | - kind: ServiceAccount 13 | name: {{ template "k8s-cloudwatch-adapter.serviceAccountName" . }} 14 | namespace: {{ .Release.Namespace }} 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: RoleBinding 18 | metadata: 19 | labels: 20 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 21 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}-auth-reader 22 | namespace: kube-system 23 | roleRef: 24 | apiGroup: rbac.authorization.k8s.io 25 | kind: Role 26 | name: extension-apiserver-authentication-reader 27 | subjects: 28 | - kind: ServiceAccount 29 | name: {{ template "k8s-cloudwatch-adapter.serviceAccountName" . }} 30 | namespace: {{ .Release.Namespace }} 31 | --- 32 | apiVersion: rbac.authorization.k8s.io/v1 33 | kind: ClusterRoleBinding 34 | metadata: 35 | labels: 36 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 37 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}-resource-reader 38 | roleRef: 39 | apiGroup: rbac.authorization.k8s.io 40 | kind: ClusterRole 41 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}-resource-reader 42 | subjects: 43 | - kind: ServiceAccount 44 | name: {{ template "k8s-cloudwatch-adapter.serviceAccountName" . }} 45 | namespace: {{ .Release.Namespace }} 46 | --- 47 | apiVersion: rbac.authorization.k8s.io/v1 48 | kind: ClusterRole 49 | metadata: 50 | labels: 51 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 52 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}:external-metrics-reader 53 | rules: 54 | - apiGroups: 55 | - external.metrics.k8s.io 56 | resources: ["*"] 57 | verbs: ["*"] 58 | --- 59 | apiVersion: rbac.authorization.k8s.io/v1 60 | kind: ClusterRole 61 | metadata: 62 | labels: 63 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 64 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}-resource-reader 65 | rules: 66 | - apiGroups: 67 | - "" 68 | resources: 69 | - namespaces 70 | - pods 71 | - services 72 | - configmaps 73 | verbs: 74 | - get 75 | - list 76 | --- 77 | apiVersion: rbac.authorization.k8s.io/v1 78 | kind: ClusterRoleBinding 79 | metadata: 80 | labels: 81 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 82 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}:external-metrics-reader 83 | roleRef: 84 | apiGroup: rbac.authorization.k8s.io 85 | kind: ClusterRole 86 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }}:external-metrics-reader 87 | subjects: 88 | - kind: ServiceAccount 89 | name: horizontal-pod-autoscaler 90 | namespace: kube-system 91 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | {{- if .Values.service.annotations }} 5 | annotations: {{ toYaml .Values.service.annotations | nindent 4 }} 6 | {{- end }} 7 | labels: 8 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 9 | {{- if .Values.service.labels }} 10 | {{- toYaml .Values.service.labels | nindent 4 }} 11 | {{- end }} 12 | name: {{ include "k8s-cloudwatch-adapter.fullname" . }} 13 | spec: 14 | ports: 15 | - name: https 16 | port: 443 17 | targetPort: 6443 18 | - name: http 19 | port: 80 20 | targetPort: 8080 21 | selector: 22 | {{- include "k8s-cloudwatch-adapter.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | kind: ServiceAccount 3 | apiVersion: v1 4 | metadata: 5 | labels: 6 | {{- include "k8s-cloudwatch-adapter.labels" . | nindent 4 }} 7 | annotations: 8 | {{- if .Values.serviceAccount.irsaIamRole }} 9 | eks.amazonaws.com/role-arn: {{ .Values.serviceAccount.irsaIamRole }} 10 | {{- end }} 11 | name: {{ template "k8s-cloudwatch-adapter.serviceAccountName" . }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/k8s-cloudwatch-adapter/values.yaml: -------------------------------------------------------------------------------- 1 | image: 2 | pullPolicy: IfNotPresent 3 | repository: chankh/k8s-cloudwatch-adapter 4 | tag: "v0.10.0" 5 | 6 | ## Optional array of imagePullSecrets containing private registry credentials 7 | ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ 8 | imagePullSecrets: [] 9 | # - name: secretName 10 | 11 | serviceAccount: 12 | create: true 13 | name: 14 | irsaIamRole: "" # e.g. "arn:aws:iam::012345678901:role/role-name" 15 | 16 | ## Runtime arguments 17 | args: 18 | cert-dir: /tmp 19 | secure-port: 6443 20 | logtostderr: true 21 | v: 2 22 | 23 | replicaCount: 1 24 | 25 | ## Labels to be added to the adapter Deployment 26 | ## 27 | labels: {} 28 | 29 | ## Annotations to be added to the adapter pods 30 | ## 31 | podAnnotations: {} 32 | 33 | ## Labels to be added to the adapter pods 34 | ## 35 | podLabels: {} 36 | 37 | ## Provide a priority class name the Deployment 38 | ## 39 | priorityClassName: "" 40 | 41 | service: 42 | ## Labels to be added to the adapter Service 43 | ## 44 | labels: {} 45 | 46 | ## Annotations to be added to the adapter Service 47 | ## 48 | annotations: {} 49 | 50 | resources: 51 | limits: 52 | cpu: 1 53 | memory: 256Mi 54 | requests: 55 | cpu: 1 56 | memory: 256Mi 57 | -------------------------------------------------------------------------------- /cmd/adapter/adapter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "time" 7 | 8 | "github.com/pkg/errors" 9 | "k8s.io/component-base/logs" 10 | "k8s.io/klog" 11 | 12 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/aws" 13 | clientset "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned" 14 | informers "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions" 15 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/controller" 16 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/metriccache" 17 | cwprov "github.com/awslabs/k8s-cloudwatch-adapter/pkg/provider" 18 | basecmd "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/cmd" 19 | "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" 20 | ) 21 | 22 | // CloudWatchAdapter represents a custom metrics BaseAdapter for Amazon CloudWatch 23 | type CloudWatchAdapter struct { 24 | basecmd.AdapterBase 25 | } 26 | 27 | func (a *CloudWatchAdapter) makeCloudWatchManager() (aws.CloudWatchManager, error) { 28 | manager := aws.NewCloudWatchManager() 29 | return manager, nil 30 | } 31 | 32 | func (a *CloudWatchAdapter) newController(cache *metriccache.MetricCache) (*controller.Controller, informers.SharedInformerFactory) { 33 | clientConfig, err := a.ClientConfig() 34 | if err != nil { 35 | klog.Fatalf("unable to construct client config: %v", err) 36 | } 37 | adapterClientSet, err := clientset.NewForConfig(clientConfig) 38 | if err != nil { 39 | klog.Fatalf("unable to construct lister client to initialize provider: %v", err) 40 | } 41 | 42 | adapterInformerFactory := informers.NewSharedInformerFactory(adapterClientSet, time.Second*30) 43 | handler := controller.NewHandler( 44 | adapterInformerFactory.Metrics().V1alpha1().ExternalMetrics().Lister(), 45 | cache) 46 | 47 | ctrl := controller.NewController(adapterInformerFactory.Metrics().V1alpha1().ExternalMetrics(), &handler) 48 | 49 | return ctrl, adapterInformerFactory 50 | } 51 | 52 | func (a *CloudWatchAdapter) makeProvider(cwManager aws.CloudWatchManager, cache *metriccache.MetricCache) (provider.ExternalMetricsProvider, error) { 53 | client, err := a.DynamicClient() 54 | if err != nil { 55 | return nil, errors.Wrap(err, "unable to construct Kubernetes client") 56 | } 57 | 58 | mapper, err := a.RESTMapper() 59 | if err != nil { 60 | return nil, errors.Wrap(err, "unable to construct RESTMapper") 61 | } 62 | 63 | cwProvider := cwprov.NewCloudWatchProvider(client, mapper, cwManager, cache) 64 | return cwProvider, nil 65 | } 66 | 67 | func main() { 68 | logs.InitLogs() 69 | defer logs.FlushLogs() 70 | 71 | // set up flags 72 | cmd := &CloudWatchAdapter{} 73 | cmd.Name = "k8s-cloudwatch-adapter" 74 | cmd.Flags().AddGoFlagSet(flag.CommandLine) // make sure we get the klog flags 75 | cmd.Flags().Parse(os.Args) 76 | 77 | stopCh := make(chan struct{}) 78 | defer close(stopCh) 79 | 80 | cache := metriccache.NewMetricCache() 81 | 82 | // start and run controller components 83 | ctrl, adapterInformerFactory := cmd.newController(cache) 84 | go adapterInformerFactory.Start(stopCh) 85 | go ctrl.Run(2, time.Second, stopCh) 86 | 87 | // create CloudWatch client 88 | cwClient, err := cmd.makeCloudWatchManager() 89 | if err != nil { 90 | klog.Fatalf("unable to construct CloudWatch client: %v", err) 91 | } 92 | 93 | // construct the provider 94 | cwProvider, err := cmd.makeProvider(cwClient, cache) 95 | if err != nil { 96 | klog.Fatalf("unable to construct CloudWatch metrics provider: %v", err) 97 | } 98 | 99 | cmd.WithExternalMetrics(cwProvider) 100 | 101 | klog.Info("CloudWatch metrics adapter started") 102 | 103 | if err := cmd.Run(stopCh); err != nil { 104 | klog.Fatalf("unable to run CloudWatch metrics adapter: %v", err) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.9 as alpine 2 | 3 | RUN apk add -U --no-cache ca-certificates 4 | 5 | FROM busybox 6 | COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 7 | COPY adapter / 8 | USER 1001:1001 9 | ENTRYPOINT ["/adapter", "--logtostderr=true"] 10 | -------------------------------------------------------------------------------- /deploy/adapter.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: custom-metrics 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: ClusterRoleBinding 8 | metadata: 9 | name: k8s-cloudwatch-adapter:system:auth-delegator 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: system:auth-delegator 14 | subjects: 15 | - kind: ServiceAccount 16 | name: k8s-cloudwatch-adapter 17 | namespace: custom-metrics 18 | --- 19 | apiVersion: rbac.authorization.k8s.io/v1 20 | kind: RoleBinding 21 | metadata: 22 | name: k8s-cloudwatch-adapter-auth-reader 23 | namespace: kube-system 24 | roleRef: 25 | apiGroup: rbac.authorization.k8s.io 26 | kind: Role 27 | name: extension-apiserver-authentication-reader 28 | subjects: 29 | - kind: ServiceAccount 30 | name: k8s-cloudwatch-adapter 31 | namespace: custom-metrics 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | labels: 37 | app: k8s-cloudwatch-adapter 38 | name: k8s-cloudwatch-adapter 39 | namespace: custom-metrics 40 | spec: 41 | replicas: 1 42 | selector: 43 | matchLabels: 44 | app: k8s-cloudwatch-adapter 45 | template: 46 | metadata: 47 | labels: 48 | app: k8s-cloudwatch-adapter 49 | name: k8s-cloudwatch-adapter 50 | spec: 51 | serviceAccountName: k8s-cloudwatch-adapter 52 | securityContext: 53 | fsGroup: 65534 54 | containers: 55 | - name: k8s-cloudwatch-adapter 56 | image: chankh/k8s-cloudwatch-adapter:v0.10.0 57 | args: 58 | - /adapter 59 | - --cert-dir=/tmp 60 | - --secure-port=6443 61 | - --logtostderr=true 62 | - --v=2 63 | ports: 64 | - containerPort: 6443 65 | name: https 66 | - containerPort: 8080 67 | name: http 68 | volumeMounts: 69 | - mountPath: /tmp 70 | name: temp-vol 71 | volumes: 72 | - name: temp-vol 73 | emptyDir: {} 74 | --- 75 | apiVersion: rbac.authorization.k8s.io/v1 76 | kind: ClusterRoleBinding 77 | metadata: 78 | name: k8s-cloudwatch-adapter-resource-reader 79 | roleRef: 80 | apiGroup: rbac.authorization.k8s.io 81 | kind: ClusterRole 82 | name: k8s-cloudwatch-adapter-resource-reader 83 | subjects: 84 | - kind: ServiceAccount 85 | name: k8s-cloudwatch-adapter 86 | namespace: custom-metrics 87 | --- 88 | kind: ServiceAccount 89 | apiVersion: v1 90 | metadata: 91 | name: k8s-cloudwatch-adapter 92 | namespace: custom-metrics 93 | --- 94 | apiVersion: v1 95 | kind: Service 96 | metadata: 97 | name: k8s-cloudwatch-adapter 98 | namespace: custom-metrics 99 | spec: 100 | ports: 101 | - name: https 102 | port: 443 103 | targetPort: 6443 104 | - name: http 105 | port: 80 106 | targetPort: 8080 107 | selector: 108 | app: k8s-cloudwatch-adapter 109 | --- 110 | apiVersion: apiregistration.k8s.io/v1beta1 111 | kind: APIService 112 | metadata: 113 | name: v1beta1.external.metrics.k8s.io 114 | spec: 115 | service: 116 | name: k8s-cloudwatch-adapter 117 | namespace: custom-metrics 118 | group: external.metrics.k8s.io 119 | version: v1beta1 120 | insecureSkipTLSVerify: true 121 | groupPriorityMinimum: 100 122 | versionPriority: 100 123 | --- 124 | apiVersion: rbac.authorization.k8s.io/v1 125 | kind: ClusterRole 126 | metadata: 127 | name: k8s-cloudwatch-adapter:external-metrics-reader 128 | rules: 129 | - apiGroups: 130 | - external.metrics.k8s.io 131 | resources: ["*"] 132 | verbs: ["*"] 133 | --- 134 | apiVersion: rbac.authorization.k8s.io/v1 135 | kind: ClusterRole 136 | metadata: 137 | name: k8s-cloudwatch-adapter-resource-reader 138 | rules: 139 | - apiGroups: 140 | - "" 141 | resources: 142 | - namespaces 143 | - pods 144 | - services 145 | - configmaps 146 | verbs: 147 | - get 148 | - list 149 | --- 150 | apiVersion: rbac.authorization.k8s.io/v1 151 | kind: ClusterRoleBinding 152 | metadata: 153 | name: k8s-cloudwatch-adapter:external-metrics-reader 154 | roleRef: 155 | apiGroup: rbac.authorization.k8s.io 156 | kind: ClusterRole 157 | name: k8s-cloudwatch-adapter:external-metrics-reader 158 | subjects: 159 | - kind: ServiceAccount 160 | name: horizontal-pod-autoscaler 161 | namespace: kube-system 162 | --- 163 | apiVersion: apiextensions.k8s.io/v1beta1 164 | kind: CustomResourceDefinition 165 | metadata: 166 | name: externalmetrics.metrics.aws 167 | spec: 168 | group: metrics.aws 169 | version: v1alpha1 170 | names: 171 | kind: ExternalMetric 172 | plural: externalmetrics 173 | singular: externalmetric 174 | scope: Namespaced 175 | --- 176 | apiVersion: rbac.authorization.k8s.io/v1 177 | kind: ClusterRole 178 | metadata: 179 | name: k8s-cloudwatch-adapter:crd-metrics-reader 180 | labels: 181 | app: k8s-cloudwatch-adapter 182 | rules: 183 | - apiGroups: 184 | - metrics.aws 185 | resources: 186 | - "externalmetrics" 187 | verbs: 188 | - list 189 | - get 190 | - watch 191 | --- 192 | apiVersion: rbac.authorization.k8s.io/v1 193 | kind: ClusterRoleBinding 194 | metadata: 195 | name: k8s-cloudwatch-adapter:crd-metrics-reader 196 | labels: 197 | app: k8s-cloudwatch-adapter 198 | roleRef: 199 | apiGroup: rbac.authorization.k8s.io 200 | kind: ClusterRole 201 | name: k8s-cloudwatch-adapter:crd-metrics-reader 202 | subjects: 203 | - name: k8s-cloudwatch-adapter 204 | namespace: "custom-metrics" 205 | kind: ServiceAccount 206 | -------------------------------------------------------------------------------- /deploy/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: externalmetrics.metrics.aws 5 | spec: 6 | group: metrics.aws 7 | version: v1alpha1 8 | names: 9 | kind: ExternalMetric 10 | plural: externalmetrics 11 | singular: externalmetric 12 | scope: Namespaced 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRole 16 | metadata: 17 | name: k8s-cloudwatch-adapter:crd-metrics-reader 18 | labels: 19 | app: k8s-cloudwatch-adapter 20 | rules: 21 | - apiGroups: 22 | - metrics.aws 23 | resources: 24 | - "externalmetrics" 25 | verbs: 26 | - list 27 | - get 28 | - watch 29 | --- 30 | apiVersion: rbac.authorization.k8s.io/v1 31 | kind: ClusterRoleBinding 32 | metadata: 33 | name: k8s-cloudwatch-adapter:crd-metrics-reader 34 | labels: 35 | app: k8s-cloudwatch-adapter 36 | roleRef: 37 | apiGroup: rbac.authorization.k8s.io 38 | kind: ClusterRole 39 | name: k8s-cloudwatch-adapter:crd-metrics-reader 40 | subjects: 41 | - name: k8s-cloudwatch-adapter 42 | namespace: "custom-metrics" 43 | kind: ServiceAccount 44 | -------------------------------------------------------------------------------- /docs/cross-account.md: -------------------------------------------------------------------------------- 1 | # Configuring cross account metric example 2 | 3 | The `k8s-cloudwatch-adapter` supports retrieving Amazon CloudWatch metrics from another AWS account 4 | using IAM roles. You will assign an IAM role to the `externalmetric` using `roleArn`, the adapter 5 | would then assume this role to send the query to CloudWatch. 6 | 7 | Before we start, let's assume the adapter is running with an IAM role called `k8s-cloudwatch-adapter-service-role` 8 | in Account A. To read the metrics from Account B, you will need to create an IAM role with the 9 | necessary permissions in Account B, let's call this `target-cloudwatch-role`. 10 | 11 | ```json 12 | { 13 | "Version": "2012-10-17", 14 | "Statement": [ 15 | { 16 | "Effect": "Allow", 17 | "Action": [ 18 | "cloudwatch:GetMetricData" 19 | ], 20 | "Resource": "*" 21 | }, 22 | { 23 | "Effect": "Allow", 24 | "Action": "iam:PassRole", 25 | "Resource": "arn:aws:iam:::role/target-cloudwatch-role" 26 | } 27 | ] 28 | } 29 | ``` 30 | 31 | Make sure we add our `k8s-cloudwatch-adapter-service-role` as a trusted entity so that it is 32 | able to perform `sts:AssumeRole`. 33 | 34 | ```json 35 | { 36 | "Version": "2012-10-17", 37 | "Statement": [ 38 | { 39 | "Effect": "Allow", 40 | "Principal": { 41 | "AWS": "arn:aws:iam:::role/k8s-cloudwatch-adapter-service-role" 42 | }, 43 | "Action": "sts:AssumeRole" 44 | } 45 | ] 46 | } 47 | ``` 48 | 49 | Next create a file `externalmetric.yaml` and specify the IAM role created in Account B in the 50 | `roleArn` for your external metric like this 51 | 52 | ```yaml 53 | apiVersion: metrics.aws/v1alpha1 54 | kind: ExternalMetric 55 | metadata: 56 | name: sqs-helloworld-length 57 | spec: 58 | name: sqs-helloworld-length 59 | roleArn: arn:aws:iam:::role/target-cloudwatch-role 60 | queries: 61 | - id: sqs_helloworld_length 62 | metricStat: 63 | metric: 64 | namespace: "AWS/SQS" 65 | metricName: "ApproximateNumberOfMessagesVisible" 66 | dimensions: 67 | - name: QueueName 68 | value: "helloworld" 69 | period: 60 70 | stat: Average 71 | unit: Count 72 | returnData: true 73 | ``` 74 | 75 | Deploy the external resource using 76 | 77 | ```bash 78 | $ kubectl apply -f externalmetric.yaml 79 | ``` 80 | 81 | The adapter should pick this up shortly and start retrieving metrics from CloudWatch in Account B. 82 | 83 | For details about the specification of the ExternalMetric custom resource, please check the 84 | [schema doc](schema.md). -------------------------------------------------------------------------------- /docs/schema.md: -------------------------------------------------------------------------------- 1 | # External Metric Schema 2 | 3 | ## ExternalMetric 4 | 5 | `ExternalMetric` describes an ExternalMetric resource 6 | 7 | Field|Type|Description 8 | ---|---|--- 9 | kind|string|ExternalMetric 10 | apiVersion|string|metrics.aws/v1alpha1 11 | spec|[MetricSeriesSpec](#metricseriesspec)|Holds all the specifications for this external metric. 12 | 13 | ## MetricSeriesSpec 14 | 15 | `MetricSeriesSpec` contains the specification for a metric series. 16 | 17 | Field|Type|Description 18 | ---|---|--- 19 | name|string|Name of the series 20 | roleArn|string|(Optional) ARN of the IAM role to assume. If specified, the adapter will send requests to Amazon Cloudwatch using this IAM role. 21 | region|string|(Optional) Target region to retrieve metrics from. The adapter will resolve the current region by default. 22 | queries|[MetricDataQuery](#metricdataquery)[]|Specify the CloudWatch metric queries to retrieve data for this series. 23 | 24 | ## MetricDataQuery 25 | 26 | `MetricDataQuery` represents the query structure used in CloudWatch [GetMetricData](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html) API. 27 | 28 | Field|Type|Description 29 | ---|---|--- 30 | expression|string|The math expression to be performed on the returned data, if this structure is performing a math expression. For more information about metric math expressions, see [Metric Math Syntax and Functions](http://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html#metric-math-syntax) in the Amazon CloudWatch User Guide.

Within one `MetricDataQuery` structure, you must specify either `Expression` or `MetricStat` but not both. 31 | id|string|A short name used to tie this object to the results in the response. This name must be unique within a single call to GetMetricData. If you are performing math expressions on this set of data, this name represents that data and can serve as a variable in the mathematical expression.

The valid characters are letters, numbers, and underscore. The first character must be a lowercase letter. 32 | label|string|A human-readable label for this metric or expression. This is especially useful if this is an expression, so that you know what the value represents. If the metric or expression is shown in a CloudWatch dashboard widget, the label is shown. If Label is omitted, CloudWatch generates a default. 33 | metricStat|[MetricStat](#metricstat)|The metric to be returned, along with statistics, period, and units. Use this parameter only if this object is retrieving a metric and not performing a math expression on returned data.

Within one MetricDataQuery object, you must specify either Expression or MetricStat but not both. 34 | returnData|boolean|Indicates whether to return the timestamps and raw data values of this metric. If you are performing this call just to do math expressions and do not also need the raw data returned, you can specify False. If you omit this, the default of True is used. 35 | 36 | ## MetricStat 37 | 38 | `MetricStat` defines the metric to be returned, along with the statistics, period, and units. 39 | 40 | Field|Type|Description 41 | ---|---|--- 42 | metric|[Metric](#metric)|The metric to return, including the metric name, namespace, and dimensions. 43 | period|integer|The period to use when retrieving the metric. 44 | stat|string|The statistic to return. It can include any CloudWatch statistic or extended statistic. 45 | unit|string|If you omit `unit` then all data that was collected with any unit is returned, along with the corresponding units that were specified when the data was reported to CloudWatch. If you specify a unit, the operation returns only data that was collected with that unit specified. If you specify a unit that does not match the data collected, the results of the operation are null. CloudWatch does not perform unit conversions. 46 | 47 | ## Metric 48 | 49 | `Metric` represents a specific metric. 50 | 51 | Field|Type|Description 52 | ---|---|--- 53 | dimensions|[Dimension](#dimension)[]|The dimensions for the metric. 54 | metricName|string|The name of the metric. This is a required field. 55 | namespace|string|The namespace of the metric. 56 | 57 | ## Dimension 58 | 59 | `Dimension` is a name/value pair that is part of the identity of a metric. 60 | 61 | Field|Type|Description 62 | ---|---|--- 63 | name|string|The name of the dimension. Dimension names cannot contain blank spaces or non-ASCII characters. 64 | value|string|The value of the dimension. Dimension values cannot contain blank spaces or non-ASCII characters. 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/awslabs/k8s-cloudwatch-adapter 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.33.5 7 | github.com/kubernetes-incubator/custom-metrics-apiserver v0.0.0-20200323093244-5046ce1afe6b 8 | github.com/pkg/errors v0.9.1 9 | gopkg.in/yaml.v2 v2.2.8 // indirect 10 | k8s.io/apimachinery v0.17.7 11 | k8s.io/apiserver v0.17.7 // indirect 12 | k8s.io/client-go v0.17.7 13 | k8s.io/code-generator v0.17.7 14 | k8s.io/component-base v0.17.7 15 | k8s.io/klog v1.0.0 16 | k8s.io/metrics v0.17.7 17 | ) 18 | -------------------------------------------------------------------------------- /hack/custom-boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /hack/gen-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Adapted for this project from cert-manager 4 | # License: https://github.com/jetstack/cert-manager/blob/master/LICENSE 5 | # Script: https://github.com/jetstack/cert-manager/blob/master/hack/update-deploy-gen.sh 6 | set -euo pipefail 7 | IFS=$'\n\t' 8 | 9 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}") 10 | REPO_ROOT="${SCRIPT_ROOT}/.." 11 | 12 | gen() { 13 | OUTPUT=$1 14 | VALUES_PATH="" 15 | TMP_OUTPUT=$(mktemp) 16 | mkdir -p "$(dirname ${OUTPUT})" 17 | VALUES=${2:-} 18 | if [[ ! -z "$VALUES" ]]; then 19 | VALUES_PATH="--values=${SCRIPT_ROOT}/deploy/values/${VALUES}.yaml" 20 | fi 21 | helm template \ 22 | "${REPO_ROOT}/charts/k8s-cloudwatch-adapter/" \ 23 | --namespace "custom-metrics" \ 24 | --name "k8s-cloudwatch-adapter" \ 25 | --set "fullnameOverride=k8s-cloudwatch-adapter" \ 26 | --set "createNamespaceResource=true" > "${TMP_OUTPUT}" \ 27 | ${VALUES_PATH} 28 | 29 | mv "${TMP_OUTPUT}" "${OUTPUT}" 30 | } 31 | 32 | # Additional deployment files can be generated here with the following format: 33 | # gen /path/to/deployment-file.yaml [values.yaml file name in deploy/values] 34 | # gen ${REPO_ROOT}/deploy/adapter.yaml the-values 35 | gen "${REPO_ROOT}/deploy/adapter.yaml" 36 | -------------------------------------------------------------------------------- /hack/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | CGO_ENABLED=0 go test $(go list ./... | grep -v -e '/client/' -e '/samples/' -e '/apis/') 8 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 2 | package tools 3 | 4 | import _ "k8s.io/code-generator" 5 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/.. 8 | CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SCRIPT_ROOT}; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} 9 | 10 | # generate the code with: 11 | # --output-base because this script should also be able to run inside the vendor dir of 12 | # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir 13 | # instead of the $GOPATH directly. For normal projects this can be dropped. 14 | chmod +x ${CODEGEN_PKG}/generate-groups.sh 15 | ${CODEGEN_PKG}/generate-groups.sh "all" \ 16 | github.com/awslabs/k8s-cloudwatch-adapter/pkg/client \ 17 | github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis \ 18 | metrics:v1alpha1 \ 19 | --go-header-file "$(dirname ${BASH_SOURCE})/custom-boilerplate.go.txt" \ 20 | --output-base "$(dirname ${BASH_SOURCE})/../../../.." 21 | -------------------------------------------------------------------------------- /hack/verify-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Modified under license: 4 | # 5 | # Copyright 2017 The Kubernetes Authors. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | set -o errexit 20 | set -o nounset 21 | set -o pipefail 22 | 23 | PROJECT_ROOT=$(dirname "${BASH_SOURCE}")/.. 24 | 25 | DIFFROOT="${PROJECT_ROOT}/pkg/apis" 26 | TMP_DIFFROOT="${PROJECT_ROOT}/_tmp/pkg/apis" 27 | _tmp="${PROJECT_ROOT}/_tmp" 28 | 29 | cleanup() { 30 | rm -rf "${_tmp}" 31 | } 32 | trap "cleanup" EXIT SIGINT 33 | 34 | cleanup 35 | 36 | mkdir -p "${TMP_DIFFROOT}" 37 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" 38 | 39 | "${PROJECT_ROOT}/hack/update-codegen.sh" 40 | echo "diffing ${DIFFROOT} against freshly generated codegen" 41 | ret=0 42 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? 43 | cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" 44 | if [[ $ret -eq 0 ]] 45 | then 46 | echo "${DIFFROOT} up to date." 47 | else 48 | echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" 49 | exit 1 50 | fi 51 | -------------------------------------------------------------------------------- /hack/verify-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Adapted for this project from cert-manager 4 | # License: https://github.com/jetstack/cert-manager/blob/master/LICENSE 5 | # Script: https://github.com/jetstack/cert-manager/blob/master/hack/verify-deploy-gen.sh 6 | 7 | set -o errexit 8 | set -o nounset 9 | set -o pipefail 10 | 11 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/.. 12 | 13 | DIFFROOT="${SCRIPT_ROOT}/deploy" 14 | TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/deploy/manifests" 15 | _tmp="${SCRIPT_ROOT}/_tmp" 16 | 17 | cleanup() { 18 | rm -rf "${_tmp}" 19 | } 20 | trap "cleanup" EXIT SIGINT 21 | 22 | cleanup 23 | 24 | mkdir -p "${TMP_DIFFROOT}" 25 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" 26 | 27 | "${SCRIPT_ROOT}/hack/gen-deploy.sh" 28 | echo "diffing ${DIFFROOT} against freshly deploy-gen" 29 | ret=0 30 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? 31 | cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" 32 | if [[ $ret -eq 0 ]] 33 | then 34 | echo "${DIFFROOT} up to date." 35 | else 36 | echo "${DIFFROOT} is out of date. Please run hack/gen-deploy.sh" 37 | exit 1 38 | fi -------------------------------------------------------------------------------- /pkg/apis/metrics/register.go: -------------------------------------------------------------------------------- 1 | package externalmetric 2 | 3 | const ( 4 | // GroupName represents the base of this package 5 | GroupName = "metrics.aws" 6 | // Version defines the latest version published 7 | Version = "v1alpha1" 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/apis/metrics/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package 2 | 3 | // Package v1alpha1 is the v1alpha1 version of the API. 4 | // +groupName=metrics.aws 5 | package v1alpha1 6 | -------------------------------------------------------------------------------- /pkg/apis/metrics/v1alpha1/externalmetric.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +genclient 8 | // +genclient:noStatus 9 | // +genclient:skipVerbs=patch 10 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 11 | 12 | // ExternalMetric describes a ExternalMetric resource 13 | type ExternalMetric struct { 14 | // TypeMeta is the metadata for the resource, like kind and apiversion 15 | metav1.TypeMeta `json:",inline"` 16 | 17 | // ObjectMeta contains the metadata for the particular object (name, namespace, self link, 18 | // labels, etc) 19 | metav1.ObjectMeta `json:"metadata,omitempty"` 20 | 21 | // Spec is the custom resource spec 22 | Spec MetricSeriesSpec `json:"spec"` 23 | } 24 | 25 | // MetricSeriesSpec contains the specification for a metric series. 26 | type MetricSeriesSpec struct { 27 | // Name specifies the series name. 28 | Name string `json:"name"` 29 | 30 | // RoleARN indicate the ARN of IAM role to assume, this metric will be retrieved using this role. 31 | RoleARN *string `json:"roleArn,omitempty"` 32 | 33 | // Region specifies the region where metrics should be retrieved. 34 | Region *string `json:"region,omitempty"` 35 | 36 | // Queries specify the CloudWatch metrics query to retrieve data for this series. 37 | Queries []MetricDataQuery `json:"queries"` 38 | } 39 | 40 | // MetricDataQuery represents the query structure used in GetMetricData operation to CloudWatch API. 41 | type MetricDataQuery struct { 42 | // The math expression to be performed on the returned data, if this structure 43 | // is performing a math expression. For more information about metric math expressions, 44 | // see Metric Math Syntax and Functions (http://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html#metric-math-syntax) 45 | // in the Amazon CloudWatch User Guide. 46 | // 47 | // Within one MetricDataQuery structure, you must specify either Expression 48 | // or MetricStat but not both. 49 | Expression string `json:"expression,omitempty"` 50 | 51 | // A short name used to tie this structure to the results in the response. This 52 | // name must be unique within a single call to GetMetricData. If you are performing 53 | // math expressions on this set of data, this name represents that data and 54 | // can serve as a variable in the mathematical expression. The valid characters 55 | // are letters, numbers, and underscore. The first character must be a lowercase 56 | // letter. 57 | // 58 | // Id is a required field 59 | ID string `json:"id"` 60 | 61 | // A human-readable label for this metric or expression. This is especially 62 | // useful if this is an expression, so that you know what the value represents. 63 | // If the metric or expression is shown in a CloudWatch dashboard widget, the 64 | // label is shown. If Label is omitted, CloudWatch generates a default. 65 | Label string `json:"label"` 66 | 67 | // The metric to be returned, along with statistics, period, and units. Use 68 | // this parameter only if this structure is performing a data retrieval and 69 | // not performing a math expression on the returned data. 70 | // 71 | // Within one MetricDataQuery structure, you must specify either Expression 72 | // or MetricStat but not both. 73 | MetricStat MetricStat `json:"metricStat"` 74 | 75 | // Indicates whether to return the time stamps and raw data values of this metric. 76 | // If you are performing this call just to do math expressions and do not also 77 | // need the raw data returned, you can specify False. If you omit this, the 78 | // default of True is used. 79 | ReturnData *bool `json:"returnData,omitempty"` 80 | } 81 | 82 | // MetricStat defines the metric to be returned, along with the statistics, period, and units. 83 | type MetricStat struct { 84 | // The metric to return, including the metric name, namespace, and dimensions. 85 | // 86 | // Metric is a required field 87 | Metric Metric `json:"metric"` 88 | 89 | // The period to use when retrieving the metric. 90 | // 91 | // Period is a required field 92 | Period int64 `json:"period"` 93 | 94 | // The statistic to return. It can include any CloudWatch statistic or extended 95 | // statistic. 96 | // 97 | // Stat is a required field 98 | Stat string `json:"stat"` 99 | 100 | // The unit to use for the returned data points. 101 | Unit string `json:"unit"` 102 | } 103 | 104 | // Metric represents a specific metric. 105 | type Metric struct { 106 | // The dimensions for the metric. 107 | Dimensions []Dimension `json:"dimensions"` 108 | 109 | // The name of the metric. 110 | MetricName string `json:"metricName"` 111 | 112 | // The namespace of the metric. 113 | Namespace string `json:"namespace"` 114 | } 115 | 116 | // Dimension expands the identity of a metric. 117 | type Dimension struct { 118 | // The name of the dimension. 119 | // 120 | // Name is a required field 121 | Name string `json:"name"` 122 | 123 | // The value representing the dimension measurement. 124 | // 125 | // Value is a required field 126 | Value string `json:"value"` 127 | } 128 | 129 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 130 | 131 | // ExternalMetricList is a list of ExternalMetric resources 132 | type ExternalMetricList struct { 133 | metav1.TypeMeta `json:",inline"` 134 | metav1.ListMeta `json:"metadata"` 135 | 136 | Items []ExternalMetric `json:"items"` 137 | } 138 | -------------------------------------------------------------------------------- /pkg/apis/metrics/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | 8 | externalmetric "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics" 9 | ) 10 | 11 | // SchemeGroupVersion is the group version used to register these objects 12 | var SchemeGroupVersion = schema.GroupVersion{ 13 | Group: externalmetric.GroupName, 14 | Version: "v1alpha1", 15 | } 16 | 17 | // SchemeBuilder definition 18 | var ( 19 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 20 | AddToScheme = SchemeBuilder.AddToScheme 21 | ) 22 | 23 | // Resource takes an unqualified resource and returns back a Group qualified GroupResource 24 | func Resource(resource string) schema.GroupResource { 25 | return SchemeGroupVersion.WithResource(resource).GroupResource() 26 | } 27 | 28 | // addKnownTypes adds our types to the API scheme by registering 29 | func addKnownTypes(scheme *runtime.Scheme) error { 30 | scheme.AddKnownTypes( 31 | SchemeGroupVersion, 32 | &ExternalMetric{}, 33 | &ExternalMetricList{}, 34 | ) 35 | 36 | // register the type in the scheme 37 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/apis/metrics/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | // 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 | // A copy of the License is located at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // or in the "license" file accompanying this file. This file is distributed 12 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | // express or implied. See the License for the specific language governing 14 | // permissions and limitations under the License. 15 | 16 | // Code generated by deepcopy-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | ) 23 | 24 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 25 | func (in *Dimension) DeepCopyInto(out *Dimension) { 26 | *out = *in 27 | return 28 | } 29 | 30 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Dimension. 31 | func (in *Dimension) DeepCopy() *Dimension { 32 | if in == nil { 33 | return nil 34 | } 35 | out := new(Dimension) 36 | in.DeepCopyInto(out) 37 | return out 38 | } 39 | 40 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 41 | func (in *ExternalMetric) DeepCopyInto(out *ExternalMetric) { 42 | *out = *in 43 | out.TypeMeta = in.TypeMeta 44 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 45 | in.Spec.DeepCopyInto(&out.Spec) 46 | return 47 | } 48 | 49 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalMetric. 50 | func (in *ExternalMetric) DeepCopy() *ExternalMetric { 51 | if in == nil { 52 | return nil 53 | } 54 | out := new(ExternalMetric) 55 | in.DeepCopyInto(out) 56 | return out 57 | } 58 | 59 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 60 | func (in *ExternalMetric) DeepCopyObject() runtime.Object { 61 | if c := in.DeepCopy(); c != nil { 62 | return c 63 | } 64 | return nil 65 | } 66 | 67 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 68 | func (in *ExternalMetricList) DeepCopyInto(out *ExternalMetricList) { 69 | *out = *in 70 | out.TypeMeta = in.TypeMeta 71 | in.ListMeta.DeepCopyInto(&out.ListMeta) 72 | if in.Items != nil { 73 | in, out := &in.Items, &out.Items 74 | *out = make([]ExternalMetric, len(*in)) 75 | for i := range *in { 76 | (*in)[i].DeepCopyInto(&(*out)[i]) 77 | } 78 | } 79 | return 80 | } 81 | 82 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalMetricList. 83 | func (in *ExternalMetricList) DeepCopy() *ExternalMetricList { 84 | if in == nil { 85 | return nil 86 | } 87 | out := new(ExternalMetricList) 88 | in.DeepCopyInto(out) 89 | return out 90 | } 91 | 92 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 93 | func (in *ExternalMetricList) DeepCopyObject() runtime.Object { 94 | if c := in.DeepCopy(); c != nil { 95 | return c 96 | } 97 | return nil 98 | } 99 | 100 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 101 | func (in *Metric) DeepCopyInto(out *Metric) { 102 | *out = *in 103 | if in.Dimensions != nil { 104 | in, out := &in.Dimensions, &out.Dimensions 105 | *out = make([]Dimension, len(*in)) 106 | copy(*out, *in) 107 | } 108 | return 109 | } 110 | 111 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metric. 112 | func (in *Metric) DeepCopy() *Metric { 113 | if in == nil { 114 | return nil 115 | } 116 | out := new(Metric) 117 | in.DeepCopyInto(out) 118 | return out 119 | } 120 | 121 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 122 | func (in *MetricDataQuery) DeepCopyInto(out *MetricDataQuery) { 123 | *out = *in 124 | in.MetricStat.DeepCopyInto(&out.MetricStat) 125 | if in.ReturnData != nil { 126 | in, out := &in.ReturnData, &out.ReturnData 127 | *out = new(bool) 128 | **out = **in 129 | } 130 | return 131 | } 132 | 133 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricDataQuery. 134 | func (in *MetricDataQuery) DeepCopy() *MetricDataQuery { 135 | if in == nil { 136 | return nil 137 | } 138 | out := new(MetricDataQuery) 139 | in.DeepCopyInto(out) 140 | return out 141 | } 142 | 143 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 144 | func (in *MetricSeriesSpec) DeepCopyInto(out *MetricSeriesSpec) { 145 | *out = *in 146 | if in.RoleARN != nil { 147 | in, out := &in.RoleARN, &out.RoleARN 148 | *out = new(string) 149 | **out = **in 150 | } 151 | if in.Region != nil { 152 | in, out := &in.Region, &out.Region 153 | *out = new(string) 154 | **out = **in 155 | } 156 | if in.Queries != nil { 157 | in, out := &in.Queries, &out.Queries 158 | *out = make([]MetricDataQuery, len(*in)) 159 | for i := range *in { 160 | (*in)[i].DeepCopyInto(&(*out)[i]) 161 | } 162 | } 163 | return 164 | } 165 | 166 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricSeriesSpec. 167 | func (in *MetricSeriesSpec) DeepCopy() *MetricSeriesSpec { 168 | if in == nil { 169 | return nil 170 | } 171 | out := new(MetricSeriesSpec) 172 | in.DeepCopyInto(out) 173 | return out 174 | } 175 | 176 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 177 | func (in *MetricStat) DeepCopyInto(out *MetricStat) { 178 | *out = *in 179 | in.Metric.DeepCopyInto(&out.Metric) 180 | return 181 | } 182 | 183 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricStat. 184 | func (in *MetricStat) DeepCopy() *MetricStat { 185 | if in == nil { 186 | return nil 187 | } 188 | out := new(MetricStat) 189 | in.DeepCopyInto(out) 190 | return out 191 | } 192 | -------------------------------------------------------------------------------- /pkg/aws/client.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "time" 7 | 8 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 9 | 10 | "github.com/aws/aws-sdk-go/aws/endpoints" 11 | 12 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 13 | 14 | "github.com/aws/aws-sdk-go/aws" 15 | "github.com/aws/aws-sdk-go/aws/session" 16 | "github.com/aws/aws-sdk-go/service/cloudwatch" 17 | "k8s.io/klog" 18 | ) 19 | 20 | func NewCloudWatchManager() CloudWatchManager { 21 | return &cloudwatchManager{ 22 | localRegion: GetLocalRegion(), 23 | } 24 | } 25 | 26 | type cloudwatchManager struct { 27 | localRegion string 28 | } 29 | 30 | func (c *cloudwatchManager) getClient(role, region *string) *cloudwatch.CloudWatch { 31 | // Using the Config value, create the CloudWatch client 32 | sess := session.Must(session.NewSession()) 33 | 34 | // Using the SDK's default configuration, loading additional config 35 | // and credentials values from the environment variables, shared 36 | // credentials, and shared configuration files 37 | cfg := aws.NewConfig().WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint) 38 | 39 | // check if roleARN is passed 40 | if role != nil { 41 | creds := stscreds.NewCredentials(sess, *role) 42 | cfg = cfg.WithCredentials(creds) 43 | klog.Infof("using IAM role ARN: %s", *role) 44 | } 45 | 46 | // check if region is set 47 | if region != nil { 48 | cfg = cfg.WithRegion(*region) 49 | } else if aws.StringValue(cfg.Region) == "" { 50 | cfg.Region = aws.String(c.localRegion) 51 | } 52 | klog.Infof("using AWS Region: %s", aws.StringValue(cfg.Region)) 53 | 54 | if os.Getenv("DEBUG") == "true" { 55 | cfg = cfg.WithLogLevel(aws.LogDebugWithHTTPBody) 56 | } 57 | 58 | svc := cloudwatch.New(sess, cfg) 59 | return svc 60 | } 61 | 62 | func (c *cloudwatchManager) QueryCloudWatch(request v1alpha1.ExternalMetric) ([]*cloudwatch.MetricDataResult, error) { 63 | role := request.Spec.RoleARN 64 | region := request.Spec.Region 65 | cwQuery := toCloudWatchQuery(&request) 66 | now := time.Now() 67 | endTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 0, 0, now.Location()) 68 | // CloudWatch metrics have latency, we will grab in a 5 minute window and extract the latest value 69 | startTime := endTime.Add(-5 * time.Minute) 70 | 71 | cwQuery.EndTime = &endTime 72 | cwQuery.StartTime = &startTime 73 | cwQuery.ScanBy = aws.String("TimestampDescending") 74 | 75 | req, resp := c.getClient(role, region).GetMetricDataRequest(&cwQuery) 76 | req.SetContext(context.Background()) 77 | 78 | if err := req.Send(); err != nil { 79 | klog.Errorf("err: %v", err) 80 | return []*cloudwatch.MetricDataResult{}, err 81 | } 82 | 83 | return resp.MetricDataResults, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/aws/interface.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/service/cloudwatch" 5 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 6 | ) 7 | 8 | // CloudWatchManager manages clients for Amazon CloudWatch. 9 | type CloudWatchManager interface { 10 | // Query sends a CloudWatch GetMetricDataInput to CloudWatch API for metric results. 11 | QueryCloudWatch(request v1alpha1.ExternalMetric) ([]*cloudwatch.MetricDataResult, error) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/aws/util.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/service/cloudwatch" 9 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 10 | 11 | "k8s.io/klog" 12 | ) 13 | 14 | // GetLocalRegion gets the region ID from the instance metadata. 15 | func GetLocalRegion() string { 16 | resp, err := http.Get("http://169.254.169.254/latest/meta-data/placement/availability-zone/") 17 | if err != nil { 18 | klog.Errorf("unable to get current region information, %v", err) 19 | return "" 20 | } 21 | 22 | defer resp.Body.Close() 23 | body, err := ioutil.ReadAll(resp.Body) 24 | if err != nil { 25 | klog.Errorf("cannot read response from instance metadata, %v", err) 26 | } 27 | 28 | // strip the last character from AZ to get region ID 29 | return string(body[0 : len(body)-1]) 30 | } 31 | 32 | func toCloudWatchQuery(externalMetric *v1alpha1.ExternalMetric) cloudwatch.GetMetricDataInput { 33 | queries := externalMetric.Spec.Queries 34 | 35 | cwMetricQueries := make([]*cloudwatch.MetricDataQuery, len(queries)) 36 | for i, q := range queries { 37 | q := q 38 | returnData := &q.ReturnData 39 | mdq := &cloudwatch.MetricDataQuery{ 40 | Id: &q.ID, 41 | Label: &q.Label, 42 | ReturnData: *returnData, 43 | } 44 | 45 | if len(q.Expression) == 0 { 46 | dimensions := make([]*cloudwatch.Dimension, len(q.MetricStat.Metric.Dimensions)) 47 | for j := range q.MetricStat.Metric.Dimensions { 48 | dimensions[j] = &cloudwatch.Dimension{ 49 | Name: &q.MetricStat.Metric.Dimensions[j].Name, 50 | Value: &q.MetricStat.Metric.Dimensions[j].Value, 51 | } 52 | } 53 | 54 | metric := &cloudwatch.Metric{ 55 | Dimensions: dimensions, 56 | MetricName: &q.MetricStat.Metric.MetricName, 57 | Namespace: &q.MetricStat.Metric.Namespace, 58 | } 59 | 60 | mdq.MetricStat = &cloudwatch.MetricStat{ 61 | Metric: metric, 62 | Period: &q.MetricStat.Period, 63 | Stat: &q.MetricStat.Stat, 64 | Unit: aws.String(q.MetricStat.Unit), 65 | } 66 | } else { 67 | mdq.Expression = &q.Expression 68 | } 69 | 70 | cwMetricQueries[i] = mdq 71 | } 72 | cwQuery := cloudwatch.GetMetricDataInput{ 73 | MetricDataQueries: cwMetricQueries, 74 | } 75 | 76 | return cwQuery 77 | } 78 | -------------------------------------------------------------------------------- /pkg/aws/util_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "testing" 5 | 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | api "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 10 | ) 11 | 12 | func TestToCloudWatchQuery(t *testing.T) { 13 | externalMetric := newFullExternalMetric("test") 14 | metricRequest := toCloudWatchQuery(externalMetric) 15 | 16 | // Metric Queries 17 | if len(metricRequest.MetricDataQueries) != len(externalMetric.Spec.Queries) { 18 | t.Errorf("metricRequest Queries = %v, want %v", metricRequest.MetricDataQueries, externalMetric.Spec.Queries) 19 | } 20 | 21 | for i, q := range metricRequest.MetricDataQueries { 22 | wantQueries := externalMetric.Spec.Queries[i] 23 | if q.Expression != nil && *q.Expression != wantQueries.Expression { 24 | t.Errorf("metricRequest Expression = %v, want %v", q.Expression, wantQueries.Expression) 25 | } 26 | 27 | if *q.Id != wantQueries.ID { 28 | t.Errorf("metricRequest ID = %v, want %v", q.Id, wantQueries.ID) 29 | } 30 | 31 | if *q.Label != wantQueries.Label { 32 | t.Errorf("metricRequest Label = %v, want %v", q.Label, wantQueries.Label) 33 | } 34 | 35 | qStat := q.MetricStat 36 | wantStat := wantQueries.MetricStat 37 | 38 | if qStat != nil { 39 | qMetric := qStat.Metric 40 | wantMetric := wantStat.Metric 41 | 42 | if len(qMetric.Dimensions) != len(wantMetric.Dimensions) { 43 | t.Errorf("metricRequest Dimensions = %v, want = %v", qMetric.Dimensions, wantMetric.Dimensions) 44 | } 45 | 46 | for j, d := range qMetric.Dimensions { 47 | if *d.Name != wantMetric.Dimensions[j].Name { 48 | t.Errorf("metricRequest Dimension Name = %v, want = %v", *d.Name, wantMetric.Dimensions[j].Name) 49 | } 50 | 51 | if *d.Value != wantMetric.Dimensions[j].Value { 52 | t.Errorf("metricRequest Dimension Value = %v, want = %v", *d.Value, wantMetric.Dimensions[j].Value) 53 | } 54 | } 55 | 56 | if *qMetric.MetricName != wantMetric.MetricName { 57 | t.Errorf("metricRequest MetricName = %v, want %v", *qMetric.MetricName, wantMetric.MetricName) 58 | } 59 | 60 | if *qMetric.Namespace != wantMetric.Namespace { 61 | t.Errorf("metricRequest Namespace = %v, want %v", *qMetric.Namespace, wantMetric.Namespace) 62 | } 63 | 64 | if *qStat.Period != wantStat.Period { 65 | t.Errorf("metricRequest Period = %v, want %v", *qStat.Period, wantStat.Period) 66 | } 67 | 68 | if *qStat.Stat != wantStat.Stat { 69 | t.Errorf("metricRequest Stat = %v, want %v", *qStat.Stat, wantStat.Stat) 70 | } 71 | 72 | if aws.StringValue(qStat.Unit) != wantStat.Unit { 73 | t.Errorf("metricRequest Unit = %v, want %v", qStat.Unit, wantStat.Unit) 74 | } 75 | } 76 | 77 | if q.ReturnData != wantQueries.ReturnData { 78 | t.Errorf("metricRequest ReturnData = %v, want %v", *q.ReturnData, wantQueries.ReturnData) 79 | } 80 | } 81 | } 82 | 83 | func newFullExternalMetric(name string) *api.ExternalMetric { 84 | role := "MyRoleARN" 85 | region := "Region" 86 | returnDataTrue := true 87 | returnDataFalse := false 88 | return &api.ExternalMetric{ 89 | TypeMeta: metav1.TypeMeta{APIVersion: api.SchemeGroupVersion.String(), Kind: "ExternalMetric"}, 90 | ObjectMeta: metav1.ObjectMeta{ 91 | Name: name, 92 | Namespace: metav1.NamespaceDefault, 93 | }, 94 | Spec: api.MetricSeriesSpec{ 95 | Name: "Name", 96 | RoleARN: &role, 97 | Region: ®ion, 98 | Queries: []api.MetricDataQuery{ 99 | { 100 | ID: "query1", 101 | Expression: "query2/query3", 102 | }, 103 | { 104 | ID: "query2", 105 | MetricStat: api.MetricStat{ 106 | Metric: api.Metric{ 107 | Dimensions: []api.Dimension{{ 108 | Name: "DimensionName1", 109 | Value: "DimensionValue1", 110 | }}, 111 | MetricName: "metricName1", 112 | Namespace: "namespace1", 113 | }, 114 | Period: 60, 115 | Stat: "Average", 116 | Unit: "Bytes", 117 | }, 118 | ReturnData: &returnDataTrue, 119 | }, 120 | { 121 | ID: "query3", 122 | MetricStat: api.MetricStat{ 123 | Metric: api.Metric{ 124 | Dimensions: []api.Dimension{{ 125 | Name: "DimensionName2", 126 | Value: "DimensionValue2", 127 | }, 128 | { 129 | Name: "DimensionName3", 130 | Value: "DimensionValue3", 131 | }}, 132 | MetricName: "metricName2", 133 | Namespace: "namespace2", 134 | }, 135 | Period: 60, 136 | Stat: "Sum", 137 | Unit: "Count", 138 | }, 139 | ReturnData: &returnDataFalse, 140 | }, 141 | }, 142 | }, 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package versioned 17 | 18 | import ( 19 | "fmt" 20 | 21 | metricsv1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/typed/metrics/v1alpha1" 22 | discovery "k8s.io/client-go/discovery" 23 | rest "k8s.io/client-go/rest" 24 | flowcontrol "k8s.io/client-go/util/flowcontrol" 25 | ) 26 | 27 | type Interface interface { 28 | Discovery() discovery.DiscoveryInterface 29 | MetricsV1alpha1() metricsv1alpha1.MetricsV1alpha1Interface 30 | } 31 | 32 | // Clientset contains the clients for groups. Each group has exactly one 33 | // version included in a Clientset. 34 | type Clientset struct { 35 | *discovery.DiscoveryClient 36 | metricsV1alpha1 *metricsv1alpha1.MetricsV1alpha1Client 37 | } 38 | 39 | // MetricsV1alpha1 retrieves the MetricsV1alpha1Client 40 | func (c *Clientset) MetricsV1alpha1() metricsv1alpha1.MetricsV1alpha1Interface { 41 | return c.metricsV1alpha1 42 | } 43 | 44 | // Discovery retrieves the DiscoveryClient 45 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 46 | if c == nil { 47 | return nil 48 | } 49 | return c.DiscoveryClient 50 | } 51 | 52 | // NewForConfig creates a new Clientset for the given config. 53 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 54 | // NewForConfig will generate a rate-limiter in configShallowCopy. 55 | func NewForConfig(c *rest.Config) (*Clientset, error) { 56 | configShallowCopy := *c 57 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 58 | if configShallowCopy.Burst <= 0 { 59 | return nil, fmt.Errorf("Burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 60 | } 61 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 62 | } 63 | var cs Clientset 64 | var err error 65 | cs.metricsV1alpha1, err = metricsv1alpha1.NewForConfig(&configShallowCopy) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return &cs, nil 75 | } 76 | 77 | // NewForConfigOrDie creates a new Clientset for the given config and 78 | // panics if there is an error in the config. 79 | func NewForConfigOrDie(c *rest.Config) *Clientset { 80 | var cs Clientset 81 | cs.metricsV1alpha1 = metricsv1alpha1.NewForConfigOrDie(c) 82 | 83 | cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) 84 | return &cs 85 | } 86 | 87 | // New creates a new Clientset for the given RESTClient. 88 | func New(c rest.Interface) *Clientset { 89 | var cs Clientset 90 | cs.metricsV1alpha1 = metricsv1alpha1.New(c) 91 | 92 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 93 | return &cs 94 | } 95 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | // This package has the automatically generated clientset. 17 | package versioned 18 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package fake 17 | 18 | import ( 19 | clientset "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned" 20 | metricsv1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/typed/metrics/v1alpha1" 21 | fakemetricsv1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/typed/metrics/v1alpha1/fake" 22 | "k8s.io/apimachinery/pkg/runtime" 23 | "k8s.io/apimachinery/pkg/watch" 24 | "k8s.io/client-go/discovery" 25 | fakediscovery "k8s.io/client-go/discovery/fake" 26 | "k8s.io/client-go/testing" 27 | ) 28 | 29 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 30 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 31 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 32 | // for a real clientset and is mostly useful in simple unit tests. 33 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 34 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 35 | for _, obj := range objects { 36 | if err := o.Add(obj); err != nil { 37 | panic(err) 38 | } 39 | } 40 | 41 | cs := &Clientset{tracker: o} 42 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 43 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 44 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 45 | gvr := action.GetResource() 46 | ns := action.GetNamespace() 47 | watch, err := o.Watch(gvr, ns) 48 | if err != nil { 49 | return false, nil, err 50 | } 51 | return true, watch, nil 52 | }) 53 | 54 | return cs 55 | } 56 | 57 | // Clientset implements clientset.Interface. Meant to be embedded into a 58 | // struct to get a default implementation. This makes faking out just the method 59 | // you want to test easier. 60 | type Clientset struct { 61 | testing.Fake 62 | discovery *fakediscovery.FakeDiscovery 63 | tracker testing.ObjectTracker 64 | } 65 | 66 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 67 | return c.discovery 68 | } 69 | 70 | func (c *Clientset) Tracker() testing.ObjectTracker { 71 | return c.tracker 72 | } 73 | 74 | var _ clientset.Interface = &Clientset{} 75 | 76 | // MetricsV1alpha1 retrieves the MetricsV1alpha1Client 77 | func (c *Clientset) MetricsV1alpha1() metricsv1alpha1.MetricsV1alpha1Interface { 78 | return &fakemetricsv1alpha1.FakeMetricsV1alpha1{Fake: &c.Fake} 79 | } 80 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | // This package has the automatically generated fake clientset. 17 | package fake 18 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package fake 17 | 18 | import ( 19 | metricsv1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 20 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 24 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 25 | ) 26 | 27 | var scheme = runtime.NewScheme() 28 | var codecs = serializer.NewCodecFactory(scheme) 29 | var parameterCodec = runtime.NewParameterCodec(scheme) 30 | var localSchemeBuilder = runtime.SchemeBuilder{ 31 | metricsv1alpha1.AddToScheme, 32 | } 33 | 34 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 35 | // of clientsets, like in: 36 | // 37 | // import ( 38 | // "k8s.io/client-go/kubernetes" 39 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 40 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 41 | // ) 42 | // 43 | // kclientset, _ := kubernetes.NewForConfig(c) 44 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 45 | // 46 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 47 | // correctly. 48 | var AddToScheme = localSchemeBuilder.AddToScheme 49 | 50 | func init() { 51 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 52 | utilruntime.Must(AddToScheme(scheme)) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | // This package contains the scheme of the automatically generated clientset. 17 | package scheme 18 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package scheme 17 | 18 | import ( 19 | metricsv1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 20 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 24 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 25 | ) 26 | 27 | var Scheme = runtime.NewScheme() 28 | var Codecs = serializer.NewCodecFactory(Scheme) 29 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 30 | var localSchemeBuilder = runtime.SchemeBuilder{ 31 | metricsv1alpha1.AddToScheme, 32 | } 33 | 34 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 35 | // of clientsets, like in: 36 | // 37 | // import ( 38 | // "k8s.io/client-go/kubernetes" 39 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 40 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 41 | // ) 42 | // 43 | // kclientset, _ := kubernetes.NewForConfig(c) 44 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 45 | // 46 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 47 | // correctly. 48 | var AddToScheme = localSchemeBuilder.AddToScheme 49 | 50 | func init() { 51 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 52 | utilruntime.Must(AddToScheme(Scheme)) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | // This package has the automatically generated typed clients. 17 | package v1alpha1 18 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/externalmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | "time" 20 | 21 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 22 | scheme "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/scheme" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | watch "k8s.io/apimachinery/pkg/watch" 25 | rest "k8s.io/client-go/rest" 26 | ) 27 | 28 | // ExternalMetricsGetter has a method to return a ExternalMetricInterface. 29 | // A group's client should implement this interface. 30 | type ExternalMetricsGetter interface { 31 | ExternalMetrics(namespace string) ExternalMetricInterface 32 | } 33 | 34 | // ExternalMetricInterface has methods to work with ExternalMetric resources. 35 | type ExternalMetricInterface interface { 36 | Create(*v1alpha1.ExternalMetric) (*v1alpha1.ExternalMetric, error) 37 | Update(*v1alpha1.ExternalMetric) (*v1alpha1.ExternalMetric, error) 38 | Delete(name string, options *v1.DeleteOptions) error 39 | DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error 40 | Get(name string, options v1.GetOptions) (*v1alpha1.ExternalMetric, error) 41 | List(opts v1.ListOptions) (*v1alpha1.ExternalMetricList, error) 42 | Watch(opts v1.ListOptions) (watch.Interface, error) 43 | ExternalMetricExpansion 44 | } 45 | 46 | // externalMetrics implements ExternalMetricInterface 47 | type externalMetrics struct { 48 | client rest.Interface 49 | ns string 50 | } 51 | 52 | // newExternalMetrics returns a ExternalMetrics 53 | func newExternalMetrics(c *MetricsV1alpha1Client, namespace string) *externalMetrics { 54 | return &externalMetrics{ 55 | client: c.RESTClient(), 56 | ns: namespace, 57 | } 58 | } 59 | 60 | // Get takes name of the externalMetric, and returns the corresponding externalMetric object, and an error if there is any. 61 | func (c *externalMetrics) Get(name string, options v1.GetOptions) (result *v1alpha1.ExternalMetric, err error) { 62 | result = &v1alpha1.ExternalMetric{} 63 | err = c.client.Get(). 64 | Namespace(c.ns). 65 | Resource("externalmetrics"). 66 | Name(name). 67 | VersionedParams(&options, scheme.ParameterCodec). 68 | Do(). 69 | Into(result) 70 | return 71 | } 72 | 73 | // List takes label and field selectors, and returns the list of ExternalMetrics that match those selectors. 74 | func (c *externalMetrics) List(opts v1.ListOptions) (result *v1alpha1.ExternalMetricList, err error) { 75 | var timeout time.Duration 76 | if opts.TimeoutSeconds != nil { 77 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 78 | } 79 | result = &v1alpha1.ExternalMetricList{} 80 | err = c.client.Get(). 81 | Namespace(c.ns). 82 | Resource("externalmetrics"). 83 | VersionedParams(&opts, scheme.ParameterCodec). 84 | Timeout(timeout). 85 | Do(). 86 | Into(result) 87 | return 88 | } 89 | 90 | // Watch returns a watch.Interface that watches the requested externalMetrics. 91 | func (c *externalMetrics) Watch(opts v1.ListOptions) (watch.Interface, error) { 92 | var timeout time.Duration 93 | if opts.TimeoutSeconds != nil { 94 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 95 | } 96 | opts.Watch = true 97 | return c.client.Get(). 98 | Namespace(c.ns). 99 | Resource("externalmetrics"). 100 | VersionedParams(&opts, scheme.ParameterCodec). 101 | Timeout(timeout). 102 | Watch() 103 | } 104 | 105 | // Create takes the representation of a externalMetric and creates it. Returns the server's representation of the externalMetric, and an error, if there is any. 106 | func (c *externalMetrics) Create(externalMetric *v1alpha1.ExternalMetric) (result *v1alpha1.ExternalMetric, err error) { 107 | result = &v1alpha1.ExternalMetric{} 108 | err = c.client.Post(). 109 | Namespace(c.ns). 110 | Resource("externalmetrics"). 111 | Body(externalMetric). 112 | Do(). 113 | Into(result) 114 | return 115 | } 116 | 117 | // Update takes the representation of a externalMetric and updates it. Returns the server's representation of the externalMetric, and an error, if there is any. 118 | func (c *externalMetrics) Update(externalMetric *v1alpha1.ExternalMetric) (result *v1alpha1.ExternalMetric, err error) { 119 | result = &v1alpha1.ExternalMetric{} 120 | err = c.client.Put(). 121 | Namespace(c.ns). 122 | Resource("externalmetrics"). 123 | Name(externalMetric.Name). 124 | Body(externalMetric). 125 | Do(). 126 | Into(result) 127 | return 128 | } 129 | 130 | // Delete takes name of the externalMetric and deletes it. Returns an error if one occurs. 131 | func (c *externalMetrics) Delete(name string, options *v1.DeleteOptions) error { 132 | return c.client.Delete(). 133 | Namespace(c.ns). 134 | Resource("externalmetrics"). 135 | Name(name). 136 | Body(options). 137 | Do(). 138 | Error() 139 | } 140 | 141 | // DeleteCollection deletes a collection of objects. 142 | func (c *externalMetrics) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { 143 | var timeout time.Duration 144 | if listOptions.TimeoutSeconds != nil { 145 | timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second 146 | } 147 | return c.client.Delete(). 148 | Namespace(c.ns). 149 | Resource("externalmetrics"). 150 | VersionedParams(&listOptions, scheme.ParameterCodec). 151 | Timeout(timeout). 152 | Body(options). 153 | Do(). 154 | Error() 155 | } 156 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | // Package fake has the automatically generated clients. 17 | package fake 18 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/fake/fake_externalmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package fake 17 | 18 | import ( 19 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 20 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | labels "k8s.io/apimachinery/pkg/labels" 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | watch "k8s.io/apimachinery/pkg/watch" 24 | testing "k8s.io/client-go/testing" 25 | ) 26 | 27 | // FakeExternalMetrics implements ExternalMetricInterface 28 | type FakeExternalMetrics struct { 29 | Fake *FakeMetricsV1alpha1 30 | ns string 31 | } 32 | 33 | var externalmetricsResource = schema.GroupVersionResource{Group: "metrics.aws", Version: "v1alpha1", Resource: "externalmetrics"} 34 | 35 | var externalmetricsKind = schema.GroupVersionKind{Group: "metrics.aws", Version: "v1alpha1", Kind: "ExternalMetric"} 36 | 37 | // Get takes name of the externalMetric, and returns the corresponding externalMetric object, and an error if there is any. 38 | func (c *FakeExternalMetrics) Get(name string, options v1.GetOptions) (result *v1alpha1.ExternalMetric, err error) { 39 | obj, err := c.Fake. 40 | Invokes(testing.NewGetAction(externalmetricsResource, c.ns, name), &v1alpha1.ExternalMetric{}) 41 | 42 | if obj == nil { 43 | return nil, err 44 | } 45 | return obj.(*v1alpha1.ExternalMetric), err 46 | } 47 | 48 | // List takes label and field selectors, and returns the list of ExternalMetrics that match those selectors. 49 | func (c *FakeExternalMetrics) List(opts v1.ListOptions) (result *v1alpha1.ExternalMetricList, err error) { 50 | obj, err := c.Fake. 51 | Invokes(testing.NewListAction(externalmetricsResource, externalmetricsKind, c.ns, opts), &v1alpha1.ExternalMetricList{}) 52 | 53 | if obj == nil { 54 | return nil, err 55 | } 56 | 57 | label, _, _ := testing.ExtractFromListOptions(opts) 58 | if label == nil { 59 | label = labels.Everything() 60 | } 61 | list := &v1alpha1.ExternalMetricList{ListMeta: obj.(*v1alpha1.ExternalMetricList).ListMeta} 62 | for _, item := range obj.(*v1alpha1.ExternalMetricList).Items { 63 | if label.Matches(labels.Set(item.Labels)) { 64 | list.Items = append(list.Items, item) 65 | } 66 | } 67 | return list, err 68 | } 69 | 70 | // Watch returns a watch.Interface that watches the requested externalMetrics. 71 | func (c *FakeExternalMetrics) Watch(opts v1.ListOptions) (watch.Interface, error) { 72 | return c.Fake. 73 | InvokesWatch(testing.NewWatchAction(externalmetricsResource, c.ns, opts)) 74 | 75 | } 76 | 77 | // Create takes the representation of a externalMetric and creates it. Returns the server's representation of the externalMetric, and an error, if there is any. 78 | func (c *FakeExternalMetrics) Create(externalMetric *v1alpha1.ExternalMetric) (result *v1alpha1.ExternalMetric, err error) { 79 | obj, err := c.Fake. 80 | Invokes(testing.NewCreateAction(externalmetricsResource, c.ns, externalMetric), &v1alpha1.ExternalMetric{}) 81 | 82 | if obj == nil { 83 | return nil, err 84 | } 85 | return obj.(*v1alpha1.ExternalMetric), err 86 | } 87 | 88 | // Update takes the representation of a externalMetric and updates it. Returns the server's representation of the externalMetric, and an error, if there is any. 89 | func (c *FakeExternalMetrics) Update(externalMetric *v1alpha1.ExternalMetric) (result *v1alpha1.ExternalMetric, err error) { 90 | obj, err := c.Fake. 91 | Invokes(testing.NewUpdateAction(externalmetricsResource, c.ns, externalMetric), &v1alpha1.ExternalMetric{}) 92 | 93 | if obj == nil { 94 | return nil, err 95 | } 96 | return obj.(*v1alpha1.ExternalMetric), err 97 | } 98 | 99 | // Delete takes name of the externalMetric and deletes it. Returns an error if one occurs. 100 | func (c *FakeExternalMetrics) Delete(name string, options *v1.DeleteOptions) error { 101 | _, err := c.Fake. 102 | Invokes(testing.NewDeleteAction(externalmetricsResource, c.ns, name), &v1alpha1.ExternalMetric{}) 103 | 104 | return err 105 | } 106 | 107 | // DeleteCollection deletes a collection of objects. 108 | func (c *FakeExternalMetrics) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { 109 | action := testing.NewDeleteCollectionAction(externalmetricsResource, c.ns, listOptions) 110 | 111 | _, err := c.Fake.Invokes(action, &v1alpha1.ExternalMetricList{}) 112 | return err 113 | } 114 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/fake/fake_metrics_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package fake 17 | 18 | import ( 19 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/typed/metrics/v1alpha1" 20 | rest "k8s.io/client-go/rest" 21 | testing "k8s.io/client-go/testing" 22 | ) 23 | 24 | type FakeMetricsV1alpha1 struct { 25 | *testing.Fake 26 | } 27 | 28 | func (c *FakeMetricsV1alpha1) ExternalMetrics(namespace string) v1alpha1.ExternalMetricInterface { 29 | return &FakeExternalMetrics{c, namespace} 30 | } 31 | 32 | // RESTClient returns a RESTClient that is used to communicate 33 | // with API server by this client implementation. 34 | func (c *FakeMetricsV1alpha1) RESTClient() rest.Interface { 35 | var ret *rest.RESTClient 36 | return ret 37 | } 38 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | type ExternalMetricExpansion interface{} 19 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/metrics/v1alpha1/metrics_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by client-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 20 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/scheme" 21 | rest "k8s.io/client-go/rest" 22 | ) 23 | 24 | type MetricsV1alpha1Interface interface { 25 | RESTClient() rest.Interface 26 | ExternalMetricsGetter 27 | } 28 | 29 | // MetricsV1alpha1Client is used to interact with features provided by the metrics.aws group. 30 | type MetricsV1alpha1Client struct { 31 | restClient rest.Interface 32 | } 33 | 34 | func (c *MetricsV1alpha1Client) ExternalMetrics(namespace string) ExternalMetricInterface { 35 | return newExternalMetrics(c, namespace) 36 | } 37 | 38 | // NewForConfig creates a new MetricsV1alpha1Client for the given config. 39 | func NewForConfig(c *rest.Config) (*MetricsV1alpha1Client, error) { 40 | config := *c 41 | if err := setConfigDefaults(&config); err != nil { 42 | return nil, err 43 | } 44 | client, err := rest.RESTClientFor(&config) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return &MetricsV1alpha1Client{client}, nil 49 | } 50 | 51 | // NewForConfigOrDie creates a new MetricsV1alpha1Client for the given config and 52 | // panics if there is an error in the config. 53 | func NewForConfigOrDie(c *rest.Config) *MetricsV1alpha1Client { 54 | client, err := NewForConfig(c) 55 | if err != nil { 56 | panic(err) 57 | } 58 | return client 59 | } 60 | 61 | // New creates a new MetricsV1alpha1Client for the given RESTClient. 62 | func New(c rest.Interface) *MetricsV1alpha1Client { 63 | return &MetricsV1alpha1Client{c} 64 | } 65 | 66 | func setConfigDefaults(config *rest.Config) error { 67 | gv := v1alpha1.SchemeGroupVersion 68 | config.GroupVersion = &gv 69 | config.APIPath = "/apis" 70 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 71 | 72 | if config.UserAgent == "" { 73 | config.UserAgent = rest.DefaultKubernetesUserAgent() 74 | } 75 | 76 | return nil 77 | } 78 | 79 | // RESTClient returns a RESTClient that is used to communicate 80 | // with API server by this client implementation. 81 | func (c *MetricsV1alpha1Client) RESTClient() rest.Interface { 82 | if c == nil { 83 | return nil 84 | } 85 | return c.restClient 86 | } 87 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/factory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by informer-gen. DO NOT EDIT. 15 | 16 | package externalversions 17 | 18 | import ( 19 | reflect "reflect" 20 | sync "sync" 21 | time "time" 22 | 23 | versioned "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned" 24 | internalinterfaces "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/internalinterfaces" 25 | metrics "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/metrics" 26 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | runtime "k8s.io/apimachinery/pkg/runtime" 28 | schema "k8s.io/apimachinery/pkg/runtime/schema" 29 | cache "k8s.io/client-go/tools/cache" 30 | ) 31 | 32 | // SharedInformerOption defines the functional option type for SharedInformerFactory. 33 | type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory 34 | 35 | type sharedInformerFactory struct { 36 | client versioned.Interface 37 | namespace string 38 | tweakListOptions internalinterfaces.TweakListOptionsFunc 39 | lock sync.Mutex 40 | defaultResync time.Duration 41 | customResync map[reflect.Type]time.Duration 42 | 43 | informers map[reflect.Type]cache.SharedIndexInformer 44 | // startedInformers is used for tracking which informers have been started. 45 | // This allows Start() to be called multiple times safely. 46 | startedInformers map[reflect.Type]bool 47 | } 48 | 49 | // WithCustomResyncConfig sets a custom resync period for the specified informer types. 50 | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { 51 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 52 | for k, v := range resyncConfig { 53 | factory.customResync[reflect.TypeOf(k)] = v 54 | } 55 | return factory 56 | } 57 | } 58 | 59 | // WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. 60 | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { 61 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 62 | factory.tweakListOptions = tweakListOptions 63 | return factory 64 | } 65 | } 66 | 67 | // WithNamespace limits the SharedInformerFactory to the specified namespace. 68 | func WithNamespace(namespace string) SharedInformerOption { 69 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 70 | factory.namespace = namespace 71 | return factory 72 | } 73 | } 74 | 75 | // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. 76 | func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { 77 | return NewSharedInformerFactoryWithOptions(client, defaultResync) 78 | } 79 | 80 | // NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. 81 | // Listers obtained via this SharedInformerFactory will be subject to the same filters 82 | // as specified here. 83 | // Deprecated: Please use NewSharedInformerFactoryWithOptions instead 84 | func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { 85 | return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) 86 | } 87 | 88 | // NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. 89 | func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { 90 | factory := &sharedInformerFactory{ 91 | client: client, 92 | namespace: v1.NamespaceAll, 93 | defaultResync: defaultResync, 94 | informers: make(map[reflect.Type]cache.SharedIndexInformer), 95 | startedInformers: make(map[reflect.Type]bool), 96 | customResync: make(map[reflect.Type]time.Duration), 97 | } 98 | 99 | // Apply all options 100 | for _, opt := range options { 101 | factory = opt(factory) 102 | } 103 | 104 | return factory 105 | } 106 | 107 | // Start initializes all requested informers. 108 | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { 109 | f.lock.Lock() 110 | defer f.lock.Unlock() 111 | 112 | for informerType, informer := range f.informers { 113 | if !f.startedInformers[informerType] { 114 | go informer.Run(stopCh) 115 | f.startedInformers[informerType] = true 116 | } 117 | } 118 | } 119 | 120 | // WaitForCacheSync waits for all started informers' cache were synced. 121 | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { 122 | informers := func() map[reflect.Type]cache.SharedIndexInformer { 123 | f.lock.Lock() 124 | defer f.lock.Unlock() 125 | 126 | informers := map[reflect.Type]cache.SharedIndexInformer{} 127 | for informerType, informer := range f.informers { 128 | if f.startedInformers[informerType] { 129 | informers[informerType] = informer 130 | } 131 | } 132 | return informers 133 | }() 134 | 135 | res := map[reflect.Type]bool{} 136 | for informType, informer := range informers { 137 | res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) 138 | } 139 | return res 140 | } 141 | 142 | // InternalInformerFor returns the SharedIndexInformer for obj using an internal 143 | // client. 144 | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { 145 | f.lock.Lock() 146 | defer f.lock.Unlock() 147 | 148 | informerType := reflect.TypeOf(obj) 149 | informer, exists := f.informers[informerType] 150 | if exists { 151 | return informer 152 | } 153 | 154 | resyncPeriod, exists := f.customResync[informerType] 155 | if !exists { 156 | resyncPeriod = f.defaultResync 157 | } 158 | 159 | informer = newFunc(f.client, resyncPeriod) 160 | f.informers[informerType] = informer 161 | 162 | return informer 163 | } 164 | 165 | // SharedInformerFactory provides shared informers for resources in all known 166 | // API group versions. 167 | type SharedInformerFactory interface { 168 | internalinterfaces.SharedInformerFactory 169 | ForResource(resource schema.GroupVersionResource) (GenericInformer, error) 170 | WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool 171 | 172 | Metrics() metrics.Interface 173 | } 174 | 175 | func (f *sharedInformerFactory) Metrics() metrics.Interface { 176 | return metrics.New(f, f.namespace, f.tweakListOptions) 177 | } 178 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by informer-gen. DO NOT EDIT. 15 | 16 | package externalversions 17 | 18 | import ( 19 | "fmt" 20 | 21 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | cache "k8s.io/client-go/tools/cache" 24 | ) 25 | 26 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 27 | // sharedInformers based on type 28 | type GenericInformer interface { 29 | Informer() cache.SharedIndexInformer 30 | Lister() cache.GenericLister 31 | } 32 | 33 | type genericInformer struct { 34 | informer cache.SharedIndexInformer 35 | resource schema.GroupResource 36 | } 37 | 38 | // Informer returns the SharedIndexInformer. 39 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 40 | return f.informer 41 | } 42 | 43 | // Lister returns the GenericLister. 44 | func (f *genericInformer) Lister() cache.GenericLister { 45 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 46 | } 47 | 48 | // ForResource gives generic access to a shared informer of the matching type 49 | // TODO extend this to unknown resources with a client pool 50 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 51 | switch resource { 52 | // Group=metrics.aws, Version=v1alpha1 53 | case v1alpha1.SchemeGroupVersion.WithResource("externalmetrics"): 54 | return &genericInformer{resource: resource.GroupResource(), informer: f.Metrics().V1alpha1().ExternalMetrics().Informer()}, nil 55 | 56 | } 57 | 58 | return nil, fmt.Errorf("no informer found for %v", resource) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by informer-gen. DO NOT EDIT. 15 | 16 | package internalinterfaces 17 | 18 | import ( 19 | time "time" 20 | 21 | versioned "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned" 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | cache "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 28 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 29 | 30 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 31 | type SharedInformerFactory interface { 32 | Start(stopCh <-chan struct{}) 33 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 34 | } 35 | 36 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 37 | type TweakListOptionsFunc func(*v1.ListOptions) 38 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/metrics/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by informer-gen. DO NOT EDIT. 15 | 16 | package metrics 17 | 18 | import ( 19 | internalinterfaces "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/internalinterfaces" 20 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/metrics/v1alpha1" 21 | ) 22 | 23 | // Interface provides access to each of this group's versions. 24 | type Interface interface { 25 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 26 | V1alpha1() v1alpha1.Interface 27 | } 28 | 29 | type group struct { 30 | factory internalinterfaces.SharedInformerFactory 31 | namespace string 32 | tweakListOptions internalinterfaces.TweakListOptionsFunc 33 | } 34 | 35 | // New returns a new Interface. 36 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 37 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 38 | } 39 | 40 | // V1alpha1 returns a new v1alpha1.Interface. 41 | func (g *group) V1alpha1() v1alpha1.Interface { 42 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/metrics/v1alpha1/externalmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by informer-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | time "time" 20 | 21 | metricsv1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 22 | versioned "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned" 23 | internalinterfaces "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/internalinterfaces" 24 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/listers/metrics/v1alpha1" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | cache "k8s.io/client-go/tools/cache" 29 | ) 30 | 31 | // ExternalMetricInformer provides access to a shared informer and lister for 32 | // ExternalMetrics. 33 | type ExternalMetricInformer interface { 34 | Informer() cache.SharedIndexInformer 35 | Lister() v1alpha1.ExternalMetricLister 36 | } 37 | 38 | type externalMetricInformer struct { 39 | factory internalinterfaces.SharedInformerFactory 40 | tweakListOptions internalinterfaces.TweakListOptionsFunc 41 | namespace string 42 | } 43 | 44 | // NewExternalMetricInformer constructs a new informer for ExternalMetric type. 45 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 46 | // one. This reduces memory footprint and number of connections to the server. 47 | func NewExternalMetricInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 48 | return NewFilteredExternalMetricInformer(client, namespace, resyncPeriod, indexers, nil) 49 | } 50 | 51 | // NewFilteredExternalMetricInformer constructs a new informer for ExternalMetric type. 52 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 53 | // one. This reduces memory footprint and number of connections to the server. 54 | func NewFilteredExternalMetricInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 55 | return cache.NewSharedIndexInformer( 56 | &cache.ListWatch{ 57 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 58 | if tweakListOptions != nil { 59 | tweakListOptions(&options) 60 | } 61 | return client.MetricsV1alpha1().ExternalMetrics(namespace).List(options) 62 | }, 63 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 64 | if tweakListOptions != nil { 65 | tweakListOptions(&options) 66 | } 67 | return client.MetricsV1alpha1().ExternalMetrics(namespace).Watch(options) 68 | }, 69 | }, 70 | &metricsv1alpha1.ExternalMetric{}, 71 | resyncPeriod, 72 | indexers, 73 | ) 74 | } 75 | 76 | func (f *externalMetricInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 77 | return NewFilteredExternalMetricInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 78 | } 79 | 80 | func (f *externalMetricInformer) Informer() cache.SharedIndexInformer { 81 | return f.factory.InformerFor(&metricsv1alpha1.ExternalMetric{}, f.defaultInformer) 82 | } 83 | 84 | func (f *externalMetricInformer) Lister() v1alpha1.ExternalMetricLister { 85 | return v1alpha1.NewExternalMetricLister(f.Informer().GetIndexer()) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/metrics/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by informer-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | internalinterfaces "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/internalinterfaces" 20 | ) 21 | 22 | // Interface provides access to all the informers in this group version. 23 | type Interface interface { 24 | // ExternalMetrics returns a ExternalMetricInformer. 25 | ExternalMetrics() ExternalMetricInformer 26 | } 27 | 28 | type version struct { 29 | factory internalinterfaces.SharedInformerFactory 30 | namespace string 31 | tweakListOptions internalinterfaces.TweakListOptionsFunc 32 | } 33 | 34 | // New returns a new Interface. 35 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 36 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 37 | } 38 | 39 | // ExternalMetrics returns a ExternalMetricInformer. 40 | func (v *version) ExternalMetrics() ExternalMetricInformer { 41 | return &externalMetricInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 42 | } 43 | -------------------------------------------------------------------------------- /pkg/client/listers/metrics/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by lister-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | // ExternalMetricListerExpansion allows custom methods to be added to 19 | // ExternalMetricLister. 20 | type ExternalMetricListerExpansion interface{} 21 | 22 | // ExternalMetricNamespaceListerExpansion allows custom methods to be added to 23 | // ExternalMetricNamespaceLister. 24 | type ExternalMetricNamespaceListerExpansion interface{} 25 | -------------------------------------------------------------------------------- /pkg/client/listers/metrics/v1alpha1/externalmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 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 | // A copy of the License is located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // Code generated by lister-gen. DO NOT EDIT. 15 | 16 | package v1alpha1 17 | 18 | import ( 19 | v1alpha1 "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 20 | "k8s.io/apimachinery/pkg/api/errors" 21 | "k8s.io/apimachinery/pkg/labels" 22 | "k8s.io/client-go/tools/cache" 23 | ) 24 | 25 | // ExternalMetricLister helps list ExternalMetrics. 26 | type ExternalMetricLister interface { 27 | // List lists all ExternalMetrics in the indexer. 28 | List(selector labels.Selector) (ret []*v1alpha1.ExternalMetric, err error) 29 | // ExternalMetrics returns an object that can list and get ExternalMetrics. 30 | ExternalMetrics(namespace string) ExternalMetricNamespaceLister 31 | ExternalMetricListerExpansion 32 | } 33 | 34 | // externalMetricLister implements the ExternalMetricLister interface. 35 | type externalMetricLister struct { 36 | indexer cache.Indexer 37 | } 38 | 39 | // NewExternalMetricLister returns a new ExternalMetricLister. 40 | func NewExternalMetricLister(indexer cache.Indexer) ExternalMetricLister { 41 | return &externalMetricLister{indexer: indexer} 42 | } 43 | 44 | // List lists all ExternalMetrics in the indexer. 45 | func (s *externalMetricLister) List(selector labels.Selector) (ret []*v1alpha1.ExternalMetric, err error) { 46 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 47 | ret = append(ret, m.(*v1alpha1.ExternalMetric)) 48 | }) 49 | return ret, err 50 | } 51 | 52 | // ExternalMetrics returns an object that can list and get ExternalMetrics. 53 | func (s *externalMetricLister) ExternalMetrics(namespace string) ExternalMetricNamespaceLister { 54 | return externalMetricNamespaceLister{indexer: s.indexer, namespace: namespace} 55 | } 56 | 57 | // ExternalMetricNamespaceLister helps list and get ExternalMetrics. 58 | type ExternalMetricNamespaceLister interface { 59 | // List lists all ExternalMetrics in the indexer for a given namespace. 60 | List(selector labels.Selector) (ret []*v1alpha1.ExternalMetric, err error) 61 | // Get retrieves the ExternalMetric from the indexer for a given namespace and name. 62 | Get(name string) (*v1alpha1.ExternalMetric, error) 63 | ExternalMetricNamespaceListerExpansion 64 | } 65 | 66 | // externalMetricNamespaceLister implements the ExternalMetricNamespaceLister 67 | // interface. 68 | type externalMetricNamespaceLister struct { 69 | indexer cache.Indexer 70 | namespace string 71 | } 72 | 73 | // List lists all ExternalMetrics in the indexer for a given namespace. 74 | func (s externalMetricNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ExternalMetric, err error) { 75 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 76 | ret = append(ret, m.(*v1alpha1.ExternalMetric)) 77 | }) 78 | return ret, err 79 | } 80 | 81 | // Get retrieves the ExternalMetric from the indexer for a given namespace and name. 82 | func (s externalMetricNamespaceLister) Get(name string) (*v1alpha1.ExternalMetric, error) { 83 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 84 | if err != nil { 85 | return nil, err 86 | } 87 | if !exists { 88 | return nil, errors.NewNotFound(v1alpha1.Resource("externalmetric"), name) 89 | } 90 | return obj.(*v1alpha1.ExternalMetric), nil 91 | } 92 | -------------------------------------------------------------------------------- /pkg/controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 8 | informers "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions/metrics/v1alpha1" 9 | "k8s.io/apimachinery/pkg/util/runtime" 10 | "k8s.io/apimachinery/pkg/util/wait" 11 | "k8s.io/client-go/tools/cache" 12 | "k8s.io/client-go/util/workqueue" 13 | "k8s.io/klog" 14 | ) 15 | 16 | // Controller will do the work of syncing the external metrics the metric adapter knows about. 17 | type Controller struct { 18 | metricQueue workqueue.RateLimitingInterface 19 | externalMetricSynced cache.InformerSynced 20 | enqueuer func(obj interface{}) 21 | metricHandler ControllerHandler 22 | } 23 | 24 | // NewController returns a new controller for handling external metric types 25 | func NewController(externalMetricInformer informers.ExternalMetricInformer, metricHandler ControllerHandler) *Controller { 26 | controller := &Controller{ 27 | externalMetricSynced: externalMetricInformer.Informer().HasSynced, 28 | metricQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "metrics"), 29 | metricHandler: metricHandler, 30 | } 31 | 32 | // wire up enqueue step. This provides a hook for testing enqueue step 33 | controller.enqueuer = controller.enqueueExternalMetric 34 | 35 | klog.Info("Setting up external metric event handlers") 36 | externalMetricInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 37 | AddFunc: controller.enqueuer, 38 | UpdateFunc: func(old, new interface{}) { 39 | // Watches and Informers will “sync”. 40 | // Periodically, they will deliver every 41 | // matching object in the cluster to your 42 | // Update method. 43 | // https://github.com/kubernetes/community/blob/8cafef897a22026d42f5e5bb3f104febe7e29830/contributors/devel/controllers.md 44 | controller.enqueuer(new) 45 | }, 46 | DeleteFunc: controller.enqueuer, 47 | }) 48 | 49 | return controller 50 | } 51 | 52 | // Run is the main path of execution for the controller loop 53 | func (c *Controller) Run(numberOfWorkers int, interval time.Duration, stopCh <-chan struct{}) { 54 | defer runtime.HandleCrash() 55 | defer c.metricQueue.ShutDown() 56 | 57 | klog.V(2).Info("initializing controller") 58 | 59 | // do the initial synchronization (one time) to populate resources 60 | if !cache.WaitForCacheSync(stopCh, c.externalMetricSynced) { 61 | runtime.HandleError(fmt.Errorf("error syncing controller cache")) 62 | return 63 | } 64 | 65 | klog.V(2).Infof("starting %d workers with %d interval", numberOfWorkers, interval) 66 | for i := 0; i < numberOfWorkers; i++ { 67 | go wait.Until(c.runWorker, interval, stopCh) 68 | } 69 | 70 | <-stopCh 71 | klog.Info("Shutting down workers") 72 | return 73 | } 74 | 75 | func (c *Controller) runWorker() { 76 | klog.V(2).Info("Worker starting") 77 | 78 | for c.processNextItem() { 79 | klog.V(2).Info("processing next item") 80 | } 81 | 82 | klog.V(2).Info("worker completed") 83 | } 84 | 85 | func (c *Controller) processNextItem() bool { 86 | klog.V(2).Info("processing item") 87 | 88 | rawItem, quit := c.metricQueue.Get() 89 | if quit { 90 | klog.V(2).Info("received quit signal") 91 | return false 92 | } 93 | 94 | defer c.metricQueue.Done(rawItem) 95 | 96 | var queueItem namespacedQueueItem 97 | var ok bool 98 | if queueItem, ok = rawItem.(namespacedQueueItem); !ok { 99 | // not valid key do not put back on queue 100 | c.metricQueue.Forget(rawItem) 101 | runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", rawItem)) 102 | return true 103 | } 104 | 105 | err := c.metricHandler.Process(queueItem) 106 | if err != nil { 107 | retries := c.metricQueue.NumRequeues(rawItem) 108 | if retries < 5 { 109 | klog.Errorf("Transient error with %d retries for key %s: %s", retries, rawItem, err) 110 | c.metricQueue.AddRateLimited(rawItem) 111 | return true 112 | } 113 | 114 | // something was wrong with the item on queue 115 | klog.Errorf("Max retries hit for key %s: %s", rawItem, err) 116 | c.metricQueue.Forget(rawItem) 117 | runtime.HandleError(err) 118 | return true 119 | } 120 | 121 | //if here success for get item 122 | klog.V(2).Infof("successfully processed item '%s'", queueItem) 123 | c.metricQueue.Forget(rawItem) 124 | return true 125 | } 126 | 127 | func (c *Controller) enqueueExternalMetric(obj interface{}) { 128 | var key string 129 | var err error 130 | if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { 131 | runtime.HandleError(err) 132 | return 133 | } 134 | 135 | kind := getKind(obj) 136 | 137 | klog.V(2).Infof("adding item to queue for '%s' with kind '%s'", key, kind) 138 | c.metricQueue.AddRateLimited(namespacedQueueItem{ 139 | namespaceKey: key, 140 | kind: kind, 141 | }) 142 | } 143 | 144 | type namespacedQueueItem struct { 145 | namespaceKey string 146 | kind string 147 | } 148 | 149 | func (q namespacedQueueItem) Key() string { 150 | return fmt.Sprintf("%s/%s", q.kind, q.namespaceKey) 151 | } 152 | 153 | func getKind(obj interface{}) string { 154 | // Due to this issue https://github.com/kubernetes/apiextensions-apiserver/issues/29 155 | // metadata is not set on freshly set CRD's 156 | // So the following does not work: 157 | // t, err := meta.TypeAccessor(obj) 158 | // kind := t.GetKind() // Kind will be blank 159 | // 160 | // A possible alternative to switching on type would be to use 161 | // https://github.com/kubernetes/kubernetes/blob/7f23a743e8c23ac6489340bbb34fa6f1d392db9d/pkg/kubectl/cmd/util/conversion.go 162 | // v := cmdUtil.AsDefaultVersionedOrOriginal(item, nil) 163 | // k := v.GetObjectKind().GroupVersionKind().Kind 164 | // 165 | // Instead use type to predict Kind which is good enough for our purposes: 166 | 167 | switch obj.(type) { 168 | case *v1alpha1.ExternalMetric: 169 | return "ExternalMetric" 170 | default: 171 | klog.Error("No known type of object") 172 | return "" 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /pkg/controller/controller_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | api "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 8 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/fake" 9 | informers "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/client-go/tools/cache" 14 | "k8s.io/client-go/util/workqueue" 15 | ) 16 | 17 | type controllerConfig struct { 18 | // store is the fake etcd backing store that the go client will 19 | // use to push to the controller. Add anything the controller to 20 | // process to the store 21 | store []runtime.Object 22 | externalMetricsListerCache []*api.ExternalMetric 23 | syncedFunction cache.InformerSynced 24 | enqueuer func(c *Controller) func(obj interface{}) 25 | handler ControllerHandler 26 | runtimes int 27 | } 28 | 29 | type wanted struct { 30 | keepRunning bool 31 | itemsRemaing int 32 | 33 | // number of times added two queue 34 | // will be zero if the item was forgeten 35 | enqueCount int 36 | enquedItem namespacedQueueItem 37 | } 38 | 39 | type testConfig struct { 40 | controllerConfig controllerConfig 41 | want wanted 42 | } 43 | 44 | func testExternalListerCache() []*api.ExternalMetric { 45 | var externalMetricsListerCache []*api.ExternalMetric 46 | 47 | externalMetric := newExternalMetric() 48 | externalMetricsListerCache = append(externalMetricsListerCache, externalMetric) 49 | return externalMetricsListerCache 50 | } 51 | 52 | func TestProcessRunsToCompletionWithExternalMetric(t *testing.T) { 53 | var storeObjects []runtime.Object 54 | externalMetric := newExternalMetric() 55 | storeObjects = append(storeObjects, externalMetric) 56 | 57 | testConfig := testConfig{ 58 | controllerConfig: controllerConfig{ 59 | store: storeObjects, 60 | syncedFunction: alwaysSynced, 61 | handler: successFakeHandler{}, 62 | runtimes: 1, 63 | }, 64 | want: wanted{ 65 | itemsRemaing: 0, 66 | keepRunning: true, 67 | }, 68 | } 69 | 70 | runControllerTests(testConfig, t) 71 | } 72 | 73 | func TestFailedProcessorReEnqueuesWithExternalMetrics(t *testing.T) { 74 | var storeObjects []runtime.Object 75 | externalMetric := newExternalMetric() 76 | storeObjects = append(storeObjects, externalMetric) 77 | 78 | testConfig := testConfig{ 79 | controllerConfig: controllerConfig{ 80 | store: storeObjects, 81 | syncedFunction: alwaysSynced, 82 | handler: failedFakeHandler{}, 83 | runtimes: 1, 84 | }, 85 | want: wanted{ 86 | itemsRemaing: 1, 87 | keepRunning: true, 88 | enqueCount: 2, // should be two because it got added two second time on failure 89 | enquedItem: namespacedQueueItem{ 90 | namespaceKey: "default/test", 91 | kind: "ExternalMetric", 92 | }, 93 | }, 94 | } 95 | 96 | runControllerTests(testConfig, t) 97 | } 98 | 99 | func TestRetryThenRemoveAfter5AttemptsWithExternalMetric(t *testing.T) { 100 | var storeObjects []runtime.Object 101 | externalMetric := newExternalMetric() 102 | storeObjects = append(storeObjects, externalMetric) 103 | 104 | testConfig := testConfig{ 105 | controllerConfig: controllerConfig{ 106 | store: storeObjects, 107 | syncedFunction: alwaysSynced, 108 | handler: failedFakeHandler{}, 109 | runtimes: 5, 110 | }, 111 | want: wanted{ 112 | itemsRemaing: 0, 113 | keepRunning: true, 114 | enqueCount: 0, // will be zero after it gets removed 115 | enquedItem: namespacedQueueItem{ 116 | namespaceKey: "default/test", 117 | kind: "ExternalMetric", 118 | }, 119 | }, 120 | } 121 | 122 | runControllerTests(testConfig, t) 123 | } 124 | func TestInvalidItemOnQueue(t *testing.T) { 125 | // force the queue to have anything other than a string 126 | // to exercise the invalid queue path 127 | var badenquer = func(c *Controller) func(obj interface{}) { 128 | enquer := func(obj interface{}) { 129 | 130 | // this pushes the object on instead of the key which 131 | // will cause an error 132 | c.metricQueue.AddRateLimited(obj) 133 | } 134 | 135 | return enquer 136 | } 137 | 138 | var storeObjects []runtime.Object 139 | externalMetric := newExternalMetric() 140 | storeObjects = append(storeObjects, externalMetric) 141 | 142 | testConfig := testConfig{ 143 | controllerConfig: controllerConfig{ 144 | store: storeObjects, 145 | syncedFunction: alwaysSynced, 146 | enqueuer: badenquer, 147 | handler: successFakeHandler{}, 148 | runtimes: 1, 149 | externalMetricsListerCache: testExternalListerCache(), 150 | }, 151 | want: wanted{ 152 | itemsRemaing: 0, 153 | keepRunning: true, 154 | }, 155 | } 156 | 157 | runControllerTests(testConfig, t) 158 | } 159 | 160 | func runControllerTests(testConfig testConfig, t *testing.T) { 161 | c, i := newController(testConfig.controllerConfig) 162 | 163 | stopCh := make(chan struct{}) 164 | defer close(stopCh) 165 | i.Start(stopCh) 166 | 167 | actaulRunTimes := 0 168 | keepRunning := false 169 | for actaulRunTimes < testConfig.controllerConfig.runtimes { 170 | keepRunning = c.processNextItem() 171 | actaulRunTimes++ 172 | } 173 | 174 | if actaulRunTimes != testConfig.controllerConfig.runtimes { 175 | t.Errorf("actual runtime should equal configured runtime = %v, want %v", actaulRunTimes, testConfig.controllerConfig.runtimes) 176 | } 177 | 178 | if keepRunning != testConfig.want.keepRunning { 179 | t.Errorf("should continue processing = %v, want %v", keepRunning, testConfig.want.keepRunning) 180 | } 181 | 182 | items := c.metricQueue.Len() 183 | 184 | if items != testConfig.want.itemsRemaing { 185 | t.Errorf("Items still on queue = %v, want %v", items, testConfig.want.itemsRemaing) 186 | } 187 | 188 | retrys := c.metricQueue.NumRequeues(testConfig.want.enquedItem) 189 | if retrys != testConfig.want.enqueCount { 190 | t.Errorf("Items enqueued times = %v, want %v", retrys, testConfig.want.enqueCount) 191 | } 192 | } 193 | 194 | func newController(config controllerConfig) (*Controller, informers.SharedInformerFactory) { 195 | fakeClient := fake.NewSimpleClientset(config.store...) 196 | i := informers.NewSharedInformerFactory(fakeClient, 0) 197 | 198 | c := NewController(i.Metrics().V1alpha1().ExternalMetrics(), config.handler) 199 | 200 | // override for testing 201 | c.externalMetricSynced = config.syncedFunction 202 | 203 | if config.enqueuer != nil { 204 | // override for testings 205 | c.enqueuer = config.enqueuer(c) 206 | } 207 | 208 | // override so the item gets added right away for testing with no delay 209 | c.metricQueue = workqueue.NewNamedRateLimitingQueue(NoDelyRateLimiter(), "nodelay") 210 | 211 | for _, em := range config.externalMetricsListerCache { 212 | // this will force the enqueuer to reload 213 | i.Metrics().V1alpha1().ExternalMetrics().Informer().GetIndexer().Add(em) 214 | } 215 | 216 | return c, i 217 | } 218 | 219 | func newExternalMetric() *api.ExternalMetric { 220 | returnDataTrue := true 221 | return &api.ExternalMetric{ 222 | TypeMeta: metav1.TypeMeta{APIVersion: api.SchemeGroupVersion.String(), Kind: "ExternalMetric"}, 223 | ObjectMeta: metav1.ObjectMeta{ 224 | Name: "test", 225 | Namespace: metav1.NamespaceDefault, 226 | }, 227 | Spec: api.MetricSeriesSpec{ 228 | Name: "test", 229 | Queries: []api.MetricDataQuery{{ 230 | ID: "queryID", 231 | MetricStat: api.MetricStat{ 232 | Metric: api.Metric{ 233 | Dimensions: []api.Dimension{{ 234 | Name: "DimensionName1", 235 | Value: "DimensionValue1", 236 | }}, 237 | MetricName: "metricName", 238 | Namespace: "namespace", 239 | }, 240 | Period: 60, 241 | Stat: "Average", 242 | Unit: "Count", 243 | }, 244 | ReturnData: &returnDataTrue, 245 | }}, 246 | }, 247 | } 248 | } 249 | 250 | type successFakeHandler struct{} 251 | 252 | func (h successFakeHandler) Process(key namespacedQueueItem) error { 253 | return nil 254 | } 255 | 256 | type failedFakeHandler struct{} 257 | 258 | func (h failedFakeHandler) Process(key namespacedQueueItem) error { 259 | return errors.New("this fake always fails") 260 | } 261 | 262 | var alwaysSynced = func() bool { return true } 263 | 264 | func NoDelyRateLimiter() workqueue.RateLimiter { 265 | return workqueue.NewItemExponentialFailureRateLimiter(0, 0) 266 | } 267 | -------------------------------------------------------------------------------- /pkg/controller/handler.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | 6 | listers "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/listers/metrics/v1alpha1" 7 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/metriccache" 8 | 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | "k8s.io/apimachinery/pkg/util/runtime" 11 | "k8s.io/client-go/tools/cache" 12 | "k8s.io/klog" 13 | ) 14 | 15 | // Handler processes the events from the controller for external metrics 16 | type Handler struct { 17 | externalmetricLister listers.ExternalMetricLister 18 | metriccache *metriccache.MetricCache 19 | } 20 | 21 | // NewHandler created a new handler 22 | func NewHandler(externalmetricLister listers.ExternalMetricLister, metricCache *metriccache.MetricCache) Handler { 23 | return Handler{ 24 | externalmetricLister: externalmetricLister, 25 | metriccache: metricCache, 26 | } 27 | } 28 | 29 | // ControllerHandler is a handler to process resource items 30 | type ControllerHandler interface { 31 | Process(queueItem namespacedQueueItem) error 32 | } 33 | 34 | // Process validates the item exists then stores updates the metric cached used to make requests to 35 | // cloudwatch 36 | func (h *Handler) Process(queueItem namespacedQueueItem) error { 37 | ns, name, err := cache.SplitMetaNamespaceKey(queueItem.namespaceKey) 38 | if err != nil { 39 | // not a valid key do not put back on queue 40 | runtime.HandleError(fmt.Errorf("expected namespace/name key in workqueue but got %s", queueItem.namespaceKey)) 41 | return err 42 | } 43 | 44 | switch queueItem.kind { 45 | case "ExternalMetric": 46 | return h.handleExternalMetric(ns, name, queueItem) 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (h *Handler) handleExternalMetric(ns, name string, queueItem namespacedQueueItem) error { 53 | // check if item exists 54 | klog.V(2).Infof("processing item '%s' in namespace '%s'", name, ns) 55 | externalMetricInfo, err := h.externalmetricLister.ExternalMetrics(ns).Get(name) 56 | if err != nil { 57 | if errors.IsNotFound(err) { 58 | // Then this we should remove 59 | klog.V(2).Infof("removing item from cache '%s' in namespace '%s'", name, ns) 60 | h.metriccache.Remove(queueItem.Key()) 61 | return nil 62 | } 63 | 64 | return err 65 | } 66 | 67 | klog.V(2).Infof("externalMetricInfo: %v", externalMetricInfo) 68 | klog.V(2).Infof("adding to cache item '%s' in namespace '%s'", name, ns) 69 | h.metriccache.Update(queueItem.Key(), name, *externalMetricInfo) 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/controller/handler_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | api "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 8 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/metriccache" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | 12 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/clientset/versioned/fake" 13 | informers "github.com/awslabs/k8s-cloudwatch-adapter/pkg/client/informers/externalversions" 14 | ) 15 | 16 | func getExternalKey(externalMetric *api.ExternalMetric) namespacedQueueItem { 17 | return namespacedQueueItem{ 18 | namespaceKey: fmt.Sprintf("%s/%s", externalMetric.Namespace, externalMetric.Name), 19 | kind: externalMetric.TypeMeta.Kind, 20 | } 21 | } 22 | 23 | func TestExternalMetricValueIsStored(t *testing.T) { 24 | var storeObjects []runtime.Object 25 | var externalMetricsListerCache []*api.ExternalMetric 26 | 27 | externalMetric := newFullExternalMetric("test") 28 | storeObjects = append(storeObjects, externalMetric) 29 | externalMetricsListerCache = append(externalMetricsListerCache, externalMetric) 30 | 31 | handler, cache := newHandler(storeObjects, externalMetricsListerCache) 32 | 33 | queueItem := getExternalKey(externalMetric) 34 | err := handler.Process(queueItem) 35 | 36 | if err != nil { 37 | t.Errorf("error after processing = %v, want %v", err, nil) 38 | } 39 | 40 | metricRequest, exists := cache.GetExternalMetric(externalMetric.Namespace, externalMetric.Name) 41 | 42 | if exists == false { 43 | t.Errorf("exist = %v, want %v", exists, true) 44 | } 45 | 46 | validateExternalMetricResult(metricRequest, externalMetric, t) 47 | } 48 | 49 | func TestShouldBeAbleToStoreCustomAndExternalWithSameNameAndNamespace(t *testing.T) { 50 | var storeObjects []runtime.Object 51 | var externalMetricsListerCache []*api.ExternalMetric 52 | 53 | externalMetric := newFullExternalMetric("test") 54 | storeObjects = append(storeObjects, externalMetric) 55 | externalMetricsListerCache = append(externalMetricsListerCache, externalMetric) 56 | 57 | handler, cache := newHandler(storeObjects, externalMetricsListerCache) 58 | 59 | externalItem := getExternalKey(externalMetric) 60 | err := handler.Process(externalItem) 61 | 62 | if err != nil { 63 | t.Errorf("error after processing = %v, want %v", err, nil) 64 | } 65 | 66 | metricRequest, exists := cache.GetExternalMetric(externalMetric.Namespace, externalMetric.Name) 67 | 68 | if exists == false { 69 | t.Errorf("exist = %v, want %v", exists, true) 70 | } 71 | 72 | validateExternalMetricResult(metricRequest, externalMetric, t) 73 | } 74 | 75 | func TestShouldFailOnInvalidCacheKey(t *testing.T) { 76 | var storeObjects []runtime.Object 77 | var externalMetricsListerCache []*api.ExternalMetric 78 | 79 | externalMetric := newFullExternalMetric("test") 80 | storeObjects = append(storeObjects, externalMetric) 81 | externalMetricsListerCache = append(externalMetricsListerCache, externalMetric) 82 | 83 | handler, cache := newHandler(storeObjects, externalMetricsListerCache) 84 | 85 | queueItem := namespacedQueueItem{ 86 | namespaceKey: "invalidkey/with/extrainfo", 87 | kind: "somethingwrong", 88 | } 89 | err := handler.Process(queueItem) 90 | 91 | if err == nil { 92 | t.Errorf("error after processing nil, want non nil") 93 | } 94 | 95 | _, exists := cache.GetExternalMetric(externalMetric.Namespace, externalMetric.Name) 96 | 97 | if exists == true { 98 | t.Errorf("exist = %v, want %v", exists, false) 99 | } 100 | } 101 | 102 | func TestWhenExternalItemHasBeenDeleted(t *testing.T) { 103 | var storeObjects []runtime.Object 104 | var externalMetricsListerCache []*api.ExternalMetric 105 | 106 | externalMetric := newFullExternalMetric("test") 107 | 108 | // don't put anything in the stores 109 | handler, cache := newHandler(storeObjects, externalMetricsListerCache) 110 | 111 | // add the item to the cache then test if it gets deleted 112 | queueItem := getExternalKey(externalMetric) 113 | cache.Update(queueItem.Key(), "test", api.ExternalMetric{}) 114 | 115 | err := handler.Process(queueItem) 116 | 117 | if err != nil { 118 | t.Errorf("error == %v, want nil", err) 119 | } 120 | 121 | _, exists := cache.GetExternalMetric(externalMetric.Namespace, externalMetric.Name) 122 | 123 | if exists == true { 124 | t.Errorf("exist = %v, want %v", exists, false) 125 | } 126 | } 127 | 128 | func TestWhenItemKindIsUnknown(t *testing.T) { 129 | var storeObjects []runtime.Object 130 | var externalMetricsListerCache []*api.ExternalMetric 131 | 132 | // don't put anything in the stores, as we are not looking anything up 133 | handler, cache := newHandler(storeObjects, externalMetricsListerCache) 134 | 135 | // add the item to the cache then test if it gets deleted 136 | queueItem := namespacedQueueItem{ 137 | namespaceKey: "default/unknown", 138 | kind: "Unknown", 139 | } 140 | 141 | err := handler.Process(queueItem) 142 | 143 | if err != nil { 144 | t.Errorf("error == %v, want nil", err) 145 | } 146 | 147 | _, exists := cache.GetExternalMetric("default", "unknown") 148 | 149 | if exists == true { 150 | t.Errorf("exist = %v, want %v", exists, false) 151 | } 152 | } 153 | 154 | func newHandler(storeObjects []runtime.Object, externalMetricsListerCache []*api.ExternalMetric) (Handler, *metriccache.MetricCache) { 155 | fakeClient := fake.NewSimpleClientset(storeObjects...) 156 | i := informers.NewSharedInformerFactory(fakeClient, 0) 157 | 158 | externalMetricLister := i.Metrics().V1alpha1().ExternalMetrics().Lister() 159 | 160 | for _, em := range externalMetricsListerCache { 161 | i.Metrics().V1alpha1().ExternalMetrics().Informer().GetIndexer().Add(em) 162 | } 163 | 164 | cache := metriccache.NewMetricCache() 165 | handler := NewHandler(externalMetricLister, cache) 166 | 167 | return handler, cache 168 | } 169 | 170 | func validateExternalMetricResult(metricRequest api.ExternalMetric, externalMetricInfo *api.ExternalMetric, t *testing.T) { 171 | spec := metricRequest.Spec 172 | wantSpec := externalMetricInfo.Spec 173 | if spec.Name != wantSpec.Name { 174 | t.Errorf("metricRequest Name = %s, want %s", spec.Name, wantSpec.Name) 175 | } 176 | 177 | if *spec.RoleARN != *wantSpec.RoleARN { 178 | t.Errorf("metricRequest RoleArn = %s, want %s", *spec.RoleARN, *wantSpec.RoleARN) 179 | } 180 | 181 | if *spec.Region != *wantSpec.Region { 182 | t.Errorf("metricRequest Region = %s, want %s", *spec.Region, *wantSpec.Region) 183 | } 184 | // Metric Queries 185 | if len(spec.Queries) != len(wantSpec.Queries) { 186 | t.Errorf("metricRequest Queries = %v, want %v", spec.Queries, wantSpec.Queries) 187 | } 188 | 189 | for i, q := range spec.Queries { 190 | wantQueries := wantSpec.Queries[i] 191 | if q.Expression != wantQueries.Expression { 192 | t.Errorf("metricRequest Expression = %v, want %v", q.Expression, wantQueries.Expression) 193 | } 194 | 195 | if q.ID != wantQueries.ID { 196 | t.Errorf("metricRequest ID = %v, want %v", q.ID, wantQueries.ID) 197 | } 198 | 199 | if q.Label != wantQueries.Label { 200 | t.Errorf("metricRequest Label = %v, want %v", q.Label, wantQueries.Label) 201 | } 202 | 203 | qStat := q.MetricStat 204 | wantStat := wantQueries.MetricStat 205 | 206 | qMetric := qStat.Metric 207 | wantMetric := wantStat.Metric 208 | 209 | if len(qMetric.Dimensions) != len(wantMetric.Dimensions) { 210 | t.Errorf("metricRequest Dimensions = %v, want = %v", qMetric.Dimensions, wantMetric.Dimensions) 211 | } 212 | 213 | for j, d := range qMetric.Dimensions { 214 | if d.Name != wantMetric.Dimensions[j].Name { 215 | t.Errorf("metricRequest Dimension Name = %v, want = %v", d.Name, wantMetric.Dimensions[j].Name) 216 | } 217 | 218 | if d.Value != wantMetric.Dimensions[j].Value { 219 | t.Errorf("metricRequest Dimension Value = %v, want = %v", d.Value, wantMetric.Dimensions[j].Value) 220 | } 221 | } 222 | 223 | if qMetric.MetricName != wantMetric.MetricName { 224 | t.Errorf("metricRequest MetricName = %v, want %v", qMetric.MetricName, wantMetric.MetricName) 225 | } 226 | 227 | if qMetric.Namespace != wantMetric.Namespace { 228 | t.Errorf("metricRequest Namespace = %v, want %v", qMetric.Namespace, wantMetric.Namespace) 229 | } 230 | 231 | if qStat.Period != wantStat.Period { 232 | t.Errorf("metricRequest Period = %v, want %v", qStat.Period, wantStat.Period) 233 | } 234 | 235 | if qStat.Stat != wantStat.Stat { 236 | t.Errorf("metricRequest Stat = %v, want %v", qStat.Stat, wantStat.Stat) 237 | } 238 | 239 | if qStat.Unit != wantStat.Unit { 240 | t.Errorf("metricRequest Unit = %v, want %v", qStat.Unit, wantStat.Unit) 241 | } 242 | 243 | if q.ReturnData != wantQueries.ReturnData { 244 | t.Errorf("metricRequest ReturnData = %v, want %v", q.ReturnData, wantQueries.ReturnData) 245 | } 246 | } 247 | 248 | } 249 | 250 | func newFullExternalMetric(name string) *api.ExternalMetric { 251 | role := "MyRoleARN" 252 | region := "region" 253 | returnDataTrue := true 254 | returnDataFalse := false 255 | return &api.ExternalMetric{ 256 | TypeMeta: metav1.TypeMeta{APIVersion: api.SchemeGroupVersion.String(), Kind: "ExternalMetric"}, 257 | ObjectMeta: metav1.ObjectMeta{ 258 | Name: name, 259 | Namespace: metav1.NamespaceDefault, 260 | }, 261 | Spec: api.MetricSeriesSpec{ 262 | Name: "Name", 263 | RoleARN: &role, 264 | Region: ®ion, 265 | Queries: []api.MetricDataQuery{ 266 | { 267 | ID: "query1", 268 | Expression: "query2/query3", 269 | }, 270 | { 271 | ID: "query2", 272 | MetricStat: api.MetricStat{ 273 | Metric: api.Metric{ 274 | Dimensions: []api.Dimension{{ 275 | Name: "DimensionName1", 276 | Value: "DimensionValue1", 277 | }}, 278 | MetricName: "metricName1", 279 | Namespace: "namespace1", 280 | }, 281 | Period: 60, 282 | Stat: "Average", 283 | Unit: "Bytes", 284 | }, 285 | ReturnData: &returnDataTrue, 286 | }, 287 | { 288 | ID: "query3", 289 | MetricStat: api.MetricStat{ 290 | Metric: api.Metric{ 291 | Dimensions: []api.Dimension{{ 292 | Name: "DimensionName2", 293 | Value: "DimensionValue2", 294 | }, 295 | { 296 | Name: "DimensionName3", 297 | Value: "DimensionValue3", 298 | }}, 299 | MetricName: "metricName2", 300 | Namespace: "namespace2", 301 | }, 302 | Period: 60, 303 | Stat: "Sum", 304 | Unit: "Count", 305 | }, 306 | ReturnData: &returnDataFalse, 307 | }, 308 | }, 309 | }, 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /pkg/metriccache/metric_cache.go: -------------------------------------------------------------------------------- 1 | package metriccache 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/apis/metrics/v1alpha1" 8 | 9 | "k8s.io/klog" 10 | ) 11 | 12 | // MetricCache holds the loaded metric request info in the system 13 | type MetricCache struct { 14 | metricMutex sync.RWMutex 15 | metricRequests map[string]interface{} 16 | metricNames map[string]string 17 | } 18 | 19 | // NewMetricCache creates the cache 20 | func NewMetricCache() *MetricCache { 21 | return &MetricCache{ 22 | metricRequests: make(map[string]interface{}), 23 | metricNames: make(map[string]string), 24 | } 25 | } 26 | 27 | // Update sets a metric request in the cache 28 | func (mc *MetricCache) Update(key string, name string, metricRequest interface{}) { 29 | mc.metricMutex.Lock() 30 | defer mc.metricMutex.Unlock() 31 | 32 | mc.metricRequests[key] = metricRequest 33 | mc.metricNames[key] = name 34 | } 35 | 36 | // GetExternalMetric retrieves an external metric request from the cache 37 | func (mc *MetricCache) GetExternalMetric(namespace, name string) (v1alpha1.ExternalMetric, bool) { 38 | mc.metricMutex.RLock() 39 | defer mc.metricMutex.RUnlock() 40 | 41 | key := externalMetricKey(namespace, name) 42 | metricRequest, exists := mc.metricRequests[key] 43 | if !exists { 44 | klog.V(2).Infof("metric not found %s", key) 45 | return v1alpha1.ExternalMetric{}, false 46 | } 47 | 48 | return metricRequest.(v1alpha1.ExternalMetric), true 49 | } 50 | 51 | // Remove removes a metric request from the cache 52 | func (mc *MetricCache) Remove(key string) { 53 | mc.metricMutex.Lock() 54 | defer mc.metricMutex.Unlock() 55 | 56 | delete(mc.metricRequests, key) 57 | delete(mc.metricNames, key) 58 | } 59 | 60 | // ListMetricNames retrieves a list of metric names from the cache. 61 | func (mc *MetricCache) ListMetricNames() []string { 62 | keys := make([]string, len(mc.metricNames)) 63 | for k := range mc.metricNames { 64 | keys = append(keys, mc.metricNames[k]) 65 | } 66 | 67 | return keys 68 | } 69 | 70 | func externalMetricKey(namespace string, name string) string { 71 | return fmt.Sprintf("ExternalMetric/%s/%s", namespace, name) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "sync" 5 | 6 | apimeta "k8s.io/apimachinery/pkg/api/meta" 7 | "k8s.io/client-go/dynamic" 8 | 9 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/aws" 10 | "github.com/awslabs/k8s-cloudwatch-adapter/pkg/metriccache" 11 | "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" 12 | ) 13 | 14 | // cloudwatchProvider is a implementation of provider.MetricsProvider for CloudWatch 15 | type cloudwatchProvider struct { 16 | client dynamic.Interface 17 | mapper apimeta.RESTMapper 18 | cwManager aws.CloudWatchManager 19 | 20 | valuesLock sync.RWMutex 21 | metricCache *metriccache.MetricCache 22 | } 23 | 24 | // NewCloudWatchProvider returns an instance of cloudwatchProvider 25 | func NewCloudWatchProvider(client dynamic.Interface, mapper apimeta.RESTMapper, cwManager aws.CloudWatchManager, metricCache *metriccache.MetricCache) provider.ExternalMetricsProvider { 26 | return &cloudwatchProvider{ 27 | client: client, 28 | mapper: mapper, 29 | cwManager: cwManager, 30 | metricCache: metricCache, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/provider/provider_external.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" 6 | "k8s.io/apimachinery/pkg/api/errors" 7 | "k8s.io/apimachinery/pkg/api/resource" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/labels" 10 | "k8s.io/klog" 11 | "k8s.io/metrics/pkg/apis/external_metrics" 12 | ) 13 | 14 | func (p *cloudwatchProvider) GetExternalMetric(namespace string, metricSelector labels.Selector, info provider.ExternalMetricInfo) (*external_metrics.ExternalMetricValueList, error) { 15 | // Note: 16 | // metric name and namespace is used to lookup for the CRD which contains configuration to 17 | // call cloudwatch if not found then ignored and label selector is parsed for all the metrics 18 | klog.V(0).Infof("Received request for namespace: %s, metric name: %s, metric selectors: %s", namespace, info.Metric, metricSelector.String()) 19 | 20 | _, selectable := metricSelector.Requirements() 21 | if !selectable { 22 | return nil, errors.NewBadRequest("label is set to not selectable. this should not happen") 23 | } 24 | 25 | externalRequest, found := p.metricCache.GetExternalMetric(namespace, info.Metric) 26 | if !found { 27 | return nil, errors.NewBadRequest("no metric query found") 28 | } 29 | 30 | metricValue, err := p.cwManager.QueryCloudWatch(externalRequest) 31 | if err != nil { 32 | klog.Errorf("bad request: %v", err) 33 | return nil, errors.NewBadRequest(err.Error()) 34 | } 35 | 36 | var quantity resource.Quantity 37 | if len(metricValue) == 0 || len(metricValue[0].Values) == 0 { 38 | quantity = *resource.NewMilliQuantity(0, resource.DecimalSI) 39 | } else { 40 | quantity = *resource.NewQuantity(int64(aws.Float64Value(metricValue[0].Values[0])), resource.DecimalSI) 41 | } 42 | externalMetricValue := external_metrics.ExternalMetricValue{ 43 | MetricName: info.Metric, 44 | Value: quantity, 45 | Timestamp: metav1.Now(), 46 | } 47 | 48 | matchingMetrics := []external_metrics.ExternalMetricValue{externalMetricValue} 49 | 50 | return &external_metrics.ExternalMetricValueList{ 51 | Items: matchingMetrics, 52 | }, nil 53 | } 54 | 55 | func (p *cloudwatchProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo { 56 | p.valuesLock.RLock() 57 | defer p.valuesLock.RUnlock() 58 | 59 | // not implemented yet 60 | var externalMetricsInfo []provider.ExternalMetricInfo 61 | for _, name := range p.metricCache.ListMetricNames() { 62 | // only process if name is non-empty 63 | if name != "" { 64 | info := provider.ExternalMetricInfo{ 65 | Metric: name, 66 | } 67 | externalMetricsInfo = append(externalMetricsInfo, info) 68 | } 69 | } 70 | return externalMetricsInfo 71 | } 72 | -------------------------------------------------------------------------------- /samples/sqs/Makefile: -------------------------------------------------------------------------------- 1 | all: clean build ; 2 | build: 3 | go build -o ./bin/producer ./producer/main.go 4 | go build -o ./bin/consumer ./consumer/main.go 5 | clean: 6 | go clean && rm -rf ./bin 7 | consumer-container: 8 | docker build -t chankh/sqs-consumer-sample ./consumer 9 | producer-container: 10 | docker build -t chankh/sqs-producer-sample ./producer 11 | -------------------------------------------------------------------------------- /samples/sqs/README.md: -------------------------------------------------------------------------------- 1 | # Sample Amazon SQS Application 2 | 3 | This directory contains a sample Amazon SQS application to test out `k8s-cloudwatch-adapter`. SQS 4 | producer and consumer are provided, together with the YAML files for deploying the consumer, metric 5 | configuration and HPA. 6 | 7 | Both the producer and consumer will use an Amazon SQS queue named `helloworld`. This queue will be 8 | created by the producer if it does not exist. 9 | 10 | ## Prerequisites 11 | 12 | Before starting, you need to first grant permissions to your Kubernetes worker nodes to interact 13 | with Amazon SQS queues. For simplicity, we will allow all SQS actions here, please do not do so on a 14 | production environment. 15 | 16 | ```json 17 | { 18 | "Version": "2012-10-17", 19 | "Statement": [ 20 | { 21 | "Effect": "Allow", 22 | "Action": "sqs:*", 23 | "Resource": "*" 24 | } 25 | ] 26 | } 27 | ``` 28 | 29 | ## Deploying the Amazon SQS consumer 30 | 31 | Now we can start deploying our consumer 32 | ```bash 33 | $ kubectl apply -f deploy/consumer-deployment.yaml 34 | ``` 35 | 36 | You can verify the consumer is running by executing this command. 37 | ```bash 38 | $ kubectl get deploy 39 | NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 40 | sqs-consumer 1 1 1 0 5s 41 | ``` 42 | 43 | ## Setup Amazon CloudWatch metric and HPA 44 | 45 | Next we will need to create an `ExternalMetric` resource for Amazon CloudWatch metric. This resource 46 | will tell the adapter how to retrieve metric data from Amazon CloudWatch. Here in 47 | `deploy/externalmetric.yaml` defined the query parameters used to retrieve the 48 | `ApproximateNumberOfMessagesVisible` for a SQS queue named `helloworld`. For details about how 49 | metric data query works, please refer to [CloudWatch GetMetricData 50 | API](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html). 51 | 52 | ```yaml 53 | apiVersion: metrics.aws/v1alpha1 54 | kind: ExternalMetric 55 | metadata: 56 | name: hello-queue-length 57 | spec: 58 | name: hello-queue-length 59 | queries: 60 | - id: sqs_helloworld 61 | metricStat: 62 | metric: 63 | namespace: "AWS/SQS" 64 | metricName: "ApproximateNumberOfMessagesVisible" 65 | dimensions: 66 | - name: QueueName 67 | value: "helloworld" 68 | period: 60 69 | stat: Average 70 | unit: Count 71 | returnData: true 72 | ``` 73 | 74 | Create the ExternalMetric resource 75 | ```bash 76 | $ kubectl apply -f deploy/externalmetric.yaml 77 | ``` 78 | 79 | Then setup the HPA for our consumer. 80 | ```bash 81 | $ kubectl apply -f deploy/hpa.yaml 82 | ``` 83 | 84 | ## Generate load using producer 85 | 86 | Finally, we can start generating messages to the queue. 87 | 88 | ```bash 89 | $ make producer 90 | $ ./bin/producer 91 | ``` 92 | 93 | On a separate terminal, you can now watch your HPA retrieving the queue length and start scaling the 94 | replicas. Amazon SQS now supports metrics at 1-minute interval, so you should start to see the 95 | deployment scale pretty quickly. 96 | 97 | ```bash 98 | $ kubectl get hpa sqs-consumer-scaler -w 99 | ``` 100 | 101 | ## Clean Up 102 | 103 | Once you are done with this experiment, you can delete the Kubernetes deployment and respective 104 | resources. 105 | 106 | Press `ctrl+c` to terminate the producer if it is still running. 107 | 108 | Execute the following commands to remove the consumer, external metric, HPA and Amazon SQS queue. 109 | ```bash 110 | $ kubectl delete -f deploy/hpa.yaml 111 | $ kubectl delete -f deploy/externalmetric.yaml 112 | $ kubectl delete -f deploy/consumer-deployment.yaml 113 | 114 | $ aws sqs delete-queue helloworld 115 | ``` 116 | 117 | -------------------------------------------------------------------------------- /samples/sqs/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11.4-alpine3.8 as builder 2 | 3 | WORKDIR /go/src/consumer 4 | COPY main.go . 5 | 6 | RUN apk add -U git 7 | RUN go get ./... 8 | 9 | RUN CGO_ENABLED=0 go build -a -tags netgo -o /consumer 10 | 11 | FROM alpine:3.8 12 | RUN apk update \ 13 | && apk add ca-certificates \ 14 | && rm -rf /var/cache/apk/* \ 15 | && update-ca-certificates 16 | 17 | COPY --from=builder /consumer / 18 | ENTRYPOINT ["/consumer"] 19 | -------------------------------------------------------------------------------- /samples/sqs/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "time" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/sqs" 13 | util "github.com/awslabs/k8s-cloudwatch-adapter/pkg/aws" 14 | ) 15 | 16 | func main() { 17 | // Using the SDK's default configuration, loading additional config 18 | // and credentials values from the environment variables, shared 19 | // credentials, and shared configuration files 20 | cfg := aws.NewConfig() 21 | 22 | if aws.StringValue(cfg.Region) == "" { 23 | cfg.Region = aws.String(util.GetLocalRegion()) 24 | } 25 | fmt.Println("using AWS Region:", cfg.Region) 26 | 27 | svc := sqs.New(session.Must(session.NewSession(cfg))) 28 | 29 | // Initialize and create a SQS Queue named helloworld if it doesn't exist 30 | queueName := os.Getenv("QUEUE") 31 | if queueName == "" { 32 | queueName = "helloworld" 33 | } 34 | fmt.Println("listening to queue:", queueName) 35 | 36 | req, q := svc.GetQueueUrlRequest(&sqs.GetQueueUrlInput{ 37 | QueueName: &queueName, 38 | }) 39 | req.SetContext(context.Background()) 40 | if err := req.Send(); req != nil { 41 | // handle queue creation error 42 | fmt.Println("cannot get queue:", err) 43 | } 44 | 45 | signalChan := make(chan os.Signal, 1) 46 | signal.Notify(signalChan, os.Interrupt, os.Kill) 47 | go func() { 48 | <-signalChan 49 | os.Exit(1) 50 | }() 51 | 52 | timeout := int64(20) 53 | 54 | for { 55 | req, msg := svc.ReceiveMessageRequest(&sqs.ReceiveMessageInput{ 56 | QueueUrl: q.QueueUrl, 57 | WaitTimeSeconds: &timeout, 58 | }) 59 | req.SetContext(context.Background()) 60 | if err := req.Send(); err == nil { 61 | fmt.Println("message:", msg) 62 | } else { 63 | fmt.Println("error receiving message from queue:", err) 64 | } 65 | if len(msg.Messages) > 0 { 66 | req, _ := svc.DeleteMessageRequest(&sqs.DeleteMessageInput{ 67 | QueueUrl: q.QueueUrl, 68 | ReceiptHandle: msg.Messages[0].ReceiptHandle, 69 | }) 70 | req.SetContext(context.Background()) 71 | if err := req.Send(); err != nil { 72 | fmt.Println("error deleting message from queue:", err) 73 | } 74 | } 75 | // Implement some delay here to simulate processing time 76 | time.Sleep(time.Duration(1000) * time.Millisecond) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /samples/sqs/deploy/consumer-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sqs-consumer 5 | annotations: 6 | iam.amazonaws.com/role: k8s-pod-sqs-consume 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: sqs-consumer 11 | template: 12 | metadata: 13 | labels: 14 | app: sqs-consumer 15 | spec: 16 | containers: 17 | - name: sqs-consumer 18 | image: chankh/sqs-consumer-sample 19 | -------------------------------------------------------------------------------- /samples/sqs/deploy/externalmetric.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: metrics.aws/v1alpha1 2 | kind: ExternalMetric 3 | metadata: 4 | name: sqs-helloworld-length 5 | spec: 6 | name: sqs-helloworld-length 7 | queries: 8 | - id: sqs_helloworld_length 9 | metricStat: 10 | metric: 11 | namespace: "AWS/SQS" 12 | metricName: "ApproximateNumberOfMessagesVisible" 13 | dimensions: 14 | - name: QueueName 15 | value: "helloworld" 16 | period: 60 17 | stat: Average 18 | unit: Count 19 | returnData: true 20 | -------------------------------------------------------------------------------- /samples/sqs/deploy/hpa.yaml: -------------------------------------------------------------------------------- 1 | kind: HorizontalPodAutoscaler 2 | apiVersion: autoscaling/v2beta1 3 | metadata: 4 | name: sqs-consumer-scaler 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1beta1 8 | kind: Deployment 9 | name: sqs-consumer 10 | minReplicas: 1 11 | maxReplicas: 10 12 | metrics: 13 | - type: External 14 | external: 15 | metricName: sqs-helloworld-length 16 | targetAverageValue: 30 17 | -------------------------------------------------------------------------------- /samples/sqs/deploy/producer-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sqs-producer 5 | annotations: 6 | iam.amazonaws.com/role: k8s-pod-sqs-produce 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: sqs-producer 11 | template: 12 | metadata: 13 | labels: 14 | app: sqs-producer 15 | spec: 16 | containers: 17 | - name: sqs-producer 18 | image: chankh/sqs-producer-sample 19 | -------------------------------------------------------------------------------- /samples/sqs/producer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11.4-alpine3.8 as builder 2 | 3 | WORKDIR /go/src/producer 4 | COPY main.go . 5 | 6 | RUN apk add -U git 7 | RUN go get ./... 8 | 9 | RUN CGO_ENABLED=0 go build -a -tags netgo -o /producer 10 | 11 | FROM alpine:3.8 12 | RUN apk update \ 13 | && apk add ca-certificates \ 14 | && rm -rf /var/cache/apk/* \ 15 | && update-ca-certificates 16 | 17 | COPY --from=builder /producer / 18 | ENTRYPOINT ["/producer"] 19 | -------------------------------------------------------------------------------- /samples/sqs/producer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/sqs" 12 | util "github.com/awslabs/k8s-cloudwatch-adapter/pkg/aws" 13 | ) 14 | 15 | func main() { 16 | // Using the SDK's default configuration, loading additional config 17 | // and credentials values from the environment variables, shared 18 | // credentials, and shared configuration files 19 | cfg := aws.NewConfig() 20 | 21 | if aws.StringValue(cfg.Region) == "" { 22 | cfg.Region = aws.String(util.GetLocalRegion()) 23 | } 24 | fmt.Println("using AWS Region:", cfg.Region) 25 | 26 | svc := sqs.New(session.Must(session.NewSession(cfg))) 27 | 28 | // Initialize and create a Service Bus Queue named helloworld if it doesn't exist 29 | queueName := os.Getenv("QUEUE") 30 | if queueName == "" { 31 | queueName = "helloworld" 32 | } 33 | message := "Hello SQS." 34 | fmt.Println("creating queue: ", queueName) 35 | 36 | req, q := svc.CreateQueueRequest(&sqs.CreateQueueInput{ 37 | QueueName: &queueName, 38 | }) 39 | req.SetContext(context.Background()) 40 | if err := req.Send(); req != nil { 41 | // handle queue creation error 42 | fmt.Println("create queue: ", err) 43 | } 44 | 45 | //https: //stackoverflow.com/a/18158859/697126 46 | signalChan := make(chan os.Signal, 1) 47 | signal.Notify(signalChan, os.Interrupt, os.Kill) 48 | go func() { 49 | <-signalChan 50 | os.Exit(1) 51 | }() 52 | 53 | for i := 1; i < 20000; i++ { 54 | fmt.Println("sending message ", i) 55 | req, resp := svc.SendMessageRequest(&sqs.SendMessageInput{ 56 | MessageBody: &message, 57 | QueueUrl: q.QueueUrl, 58 | }) 59 | req.SetContext(context.Background()) 60 | if err := req.Send(); err != nil { 61 | fmt.Println("Error", err) 62 | return 63 | } 64 | 65 | fmt.Println("Success", aws.StringValue(resp.MessageId)) 66 | // time.Sleep(time.Duration(100) * time.Millisecond) 67 | } 68 | 69 | } 70 | --------------------------------------------------------------------------------