├── .gitignore ├── .mergify.yml ├── .travis.yml ├── CONTRIBUTING.md ├── DCO ├── LICENSE ├── Makefile ├── OWNERS ├── OWNERS_ALIASES ├── README.md ├── RELEASE.md ├── ROADMAP.md ├── SECURITY_CONTACTS ├── awsconn └── awsconn.go ├── book.json ├── build ├── build-release-binaries ├── builtin ├── files │ ├── cluster.yaml.tmpl │ ├── etcdadm │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README.md │ │ ├── etcdadm │ │ └── test │ ├── kubeconfig.tmpl │ ├── plugins │ │ ├── aws-iam-authenticator │ │ │ ├── files │ │ │ │ ├── authentication-token-webhook-config.yaml │ │ │ │ ├── controller-kubeconfig.yaml │ │ │ │ └── worker-kubeconfig.yaml │ │ │ ├── manifests │ │ │ │ ├── aws-auth-cm.yaml │ │ │ │ ├── clusterrole.yaml │ │ │ │ ├── clusterrolebinding.yaml │ │ │ │ ├── daemonset.yaml │ │ │ │ └── serviceaccount.yaml │ │ │ └── plugin.yaml │ │ ├── cluster-autoscaler │ │ │ ├── manifests │ │ │ │ ├── deployment.yaml │ │ │ │ ├── pdb.yaml │ │ │ │ ├── rbac.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── servicemonitor.yaml │ │ │ └── plugin.yaml │ │ ├── dashboard │ │ │ ├── manifests │ │ │ │ ├── autoscaler.yaml │ │ │ │ ├── deployment.yaml │ │ │ │ ├── metrics-scraper.yaml │ │ │ │ ├── rbac.yaml │ │ │ │ └── secrets.yaml │ │ │ └── plugin.yaml │ │ ├── kiam │ │ │ ├── manifests │ │ │ │ ├── agent-daemonset.yaml │ │ │ │ ├── agent-tls-secret.yaml │ │ │ │ ├── server-cluster-role-binding.yaml │ │ │ │ ├── server-cluster-role.yaml │ │ │ │ ├── server-daemonset.yaml │ │ │ │ ├── server-tls-secret.yaml │ │ │ │ ├── service-account.yaml │ │ │ │ └── service.yaml │ │ │ └── plugin.yaml │ │ ├── kube2iam │ │ │ ├── manifests │ │ │ │ ├── daemonset.yaml │ │ │ │ └── rbac.yaml │ │ │ └── plugin.yaml │ │ └── upgrade-helper │ │ │ ├── assets │ │ │ ├── upgrade-helper-post.sh │ │ │ └── upgrade-helper-pre.sh │ │ │ └── plugin.yaml │ ├── stack-templates │ │ ├── control-plane.json.tmpl │ │ ├── etcd.json.tmpl │ │ ├── network.json.tmpl │ │ ├── node-pool.json.tmpl │ │ └── root.json.tmpl │ └── userdata │ │ ├── cloud-config-controller │ │ ├── cloud-config-etcd │ │ └── cloud-config-worker └── packrbox.go ├── cfnresource ├── naming.go └── naming_test.go ├── cfnstack ├── assets.go ├── cfnstack.go ├── cfnstack_test.go ├── ec2.go ├── provisioner.go ├── provisioner_test.go └── s3_uri.go ├── ci ├── .gitignore └── publish-docs-as-bot.sh ├── cmd ├── apply.go ├── calculator.go ├── destroy.go ├── diff.go ├── flag_util.go ├── init.go ├── render.go ├── root.go ├── show.go ├── status.go ├── up.go ├── update.go ├── validate.go └── version.go ├── code-of-conduct.md ├── containerized-build-release-binaries ├── containerized-test-run ├── contrib ├── bump-version ├── cluster-backup │ ├── OWNERS │ ├── README.md │ └── restore.sh └── dex │ ├── README.md │ ├── dex.cm.yaml │ ├── dex.de.yaml │ ├── elb │ ├── dex.external-elb.svc.yaml │ └── dex.internal-elb.svc.yaml │ └── ingress │ ├── dex.nginx.ing.yaml │ └── dex.svc.yaml ├── core └── root │ ├── cluster.go │ ├── config │ └── config.go │ ├── credentials.go │ ├── defaults │ └── defaults.go │ ├── describer.go │ ├── destroyer.go │ ├── diff.go │ ├── generate.go │ ├── operation_targets.go │ ├── options.go │ ├── rolling_strategy.go │ ├── stack.go │ └── template_params.go ├── credential ├── credential.go ├── encrypted_assets.go ├── encrypted_assets_test.go ├── encrypted_file.go ├── generator.go ├── kms_encryptor.go ├── pki.go ├── plaintext_file.go ├── store.go └── types.go ├── docs ├── README.md ├── SUMMARY.md ├── add-ons │ ├── README.md │ ├── cluster-resource-backup-to-s3.md │ └── journald-logging-to-cloudwatch.md ├── advanced-topics │ ├── README.md │ ├── cloudformation-updates-in-cli.md │ ├── etcd-backup-and-restore.md │ ├── high-availability.md │ ├── network-topologies.md │ └── use-an-existing-vpc.md ├── cli-reference │ ├── README.md │ └── aws-credentials.md ├── getting-in-touch.md ├── getting-started │ ├── README.md │ ├── prerequisites.md │ ├── step-1-configure.md │ ├── step-2-render.md │ ├── step-3-launch.md │ ├── step-4-update.md │ ├── step-5-add-node-pool.md │ ├── step-6-configure-add-ons.md │ └── step-7-destroy.md ├── guides │ ├── README.md │ ├── developer-guide.md │ └── operator-guide.md ├── styles │ └── site.css ├── troubleshooting │ ├── README.md │ ├── common-problems.md │ └── known-limitations.md └── tutorials │ ├── README.md │ └── quick-start.md ├── e2e ├── README.md ├── kubernetes │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ └── conformance.sh ├── run └── testinfra │ └── stack-template.yaml ├── filegen ├── filegen.go └── render.go ├── filereader ├── jsontemplate │ └── jsontemplate.go └── texttemplate │ ├── texttemplate.go │ └── texttemplate_test.go ├── fingerprint ├── sha256.go └── sha256_test.go ├── flatcar └── amiregistry │ ├── amiregistry.go │ ├── reliable_http.go │ └── reliable_http_test.go ├── go.mod ├── go.sum ├── gzipcompressor └── gzipcompressor.go ├── hack ├── README.md ├── relnote ├── relnote.go └── version ├── kube-aws-bot-git-ssh-key.enc ├── licenses ├── LICENSE.ebs-automatic-nvme-mapping └── README.md ├── logger ├── color.go └── logger.go ├── main.go ├── make └── test ├── naming └── convention.go ├── netutil └── netutil.go ├── pkg ├── api │ ├── addons.go │ ├── amazon_vpc.go │ ├── api_endpoint.go │ ├── api_endpoint_lb.go │ ├── api_endpoints.go │ ├── arn.go │ ├── asg.go │ ├── asg_test.go │ ├── assets.go │ ├── aws_iam.go │ ├── bash_prompt.go │ ├── cidr_range.go │ ├── cidr_range_test.go │ ├── cloudformation.go │ ├── cloudwatch_logging.go │ ├── cluster.go │ ├── const.go │ ├── controller.go │ ├── custom_file.go │ ├── custom_file_test.go │ ├── custom_systemd_unit.go │ ├── custom_systemd_unit_dropin.go │ ├── data_volume.go │ ├── deployment.go │ ├── ec2_instance.go │ ├── etcd.go │ ├── etcd_cluster.go │ ├── etcd_node.go │ ├── etcd_test.go │ ├── existing_etcd.go │ ├── feature_gates.go │ ├── gpu.go │ ├── helm_release_fileset.go │ ├── hosted_zone.go │ ├── iamconfig.go │ ├── identifier.go │ ├── image.go │ ├── internet_gateway.go │ ├── keypair_spec.go │ ├── kubernetes.go │ ├── lauch_specification.go │ ├── mixed_instances.go │ ├── motd_banner.go │ ├── nat_gateway.go │ ├── networking.go │ ├── node_drainer.go │ ├── node_drainer_test.go │ ├── node_labels.go │ ├── node_settings.go │ ├── node_volume_mount.go │ ├── node_volume_mount_test.go │ ├── oidc.go │ ├── pki.go │ ├── plugin.go │ ├── plugin_config.go │ ├── raid0_mount.go │ ├── region.go │ ├── root_volume.go │ ├── s3_folders.go │ ├── security_group.go │ ├── shell_colour.go │ ├── shellcolour_enumer.go │ ├── spot_fleet.go │ ├── stack_name_overrides.go │ ├── subnet.go │ ├── subnet_reference.go │ ├── subnets.go │ ├── subnets_test.go │ ├── taint.go │ ├── taint_test.go │ ├── types.go │ ├── unknown_keys.go │ ├── unknown_keys_test.go │ ├── userdata.go │ ├── userdata_test.go │ ├── vpc.go │ ├── worker_node_pool.go │ └── worker_node_pool_test.go └── model │ ├── api_endpoint.go │ ├── api_endpoint_lb.go │ ├── api_endpoints.go │ ├── cluster_test.go │ ├── compiler.go │ ├── config.go │ ├── config_cluster_size_test.go │ ├── config_test.go │ ├── const.go │ ├── context.go │ ├── credentials.go │ ├── credentials_test.go │ ├── describer.go │ ├── etcd_cluster.go │ ├── etcd_cluster_test.go │ ├── etcd_node.go │ ├── etcd_nodes.go │ ├── info.go │ ├── init.go │ ├── network.go │ ├── node_pool_cluster_test.go │ ├── node_pool_compile.go │ ├── node_pool_config.go │ ├── node_pool_deployment_settings.go │ ├── node_pool_stack_info.go │ ├── node_pool_stack_ref.go │ ├── nodepool_test.go │ ├── stack.go │ ├── stack_new.go │ ├── stack_ref.go │ ├── stack_test.go │ ├── types.go │ └── user_data_config_test.go ├── pki ├── ca.go ├── cert.go ├── cert_test.go ├── keypair.go ├── pem.go ├── pem_test.go ├── pki.go ├── rsa.go ├── types.go └── x509.go ├── plugin ├── clusterextension │ └── extras.go ├── loader.go ├── plugincontents │ ├── loader.go │ ├── render_values.go │ └── template.go └── pluginutil │ └── merge_values.go ├── proposals └── plugins.md ├── provisioner ├── content.go ├── package.go ├── prepare.go ├── remote_file.go ├── remote_file_loader.go ├── remote_file_spec.go ├── transfer.go └── types.go ├── test ├── helper │ ├── cfn.go │ ├── encrypt_service.go │ ├── helper.go │ ├── plugin.go │ └── s3.go └── integration │ ├── aws_test.go │ ├── maincluster_test.go │ └── plugin_test.go └── tmpl ├── text_to_cfn_expr.go ├── tmpl.go └── tmpl_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | kube-aws 2 | /artifacts/template.json 3 | /bin 4 | /e2e/assets 5 | *~ 6 | /core/*/config/templates.go 7 | /core/*/config/files.go 8 | .idea/ 9 | .envrc 10 | coverage.txt 11 | profile.out 12 | test-result.json 13 | pkg/model/cache 14 | builtin/a_builtin-packr.go 15 | 16 | # gitbook docs 17 | _book 18 | .grunt 19 | *.epub 20 | *.mobi 21 | *.pdf 22 | node_modules 23 | kube-aws 24 | .DS_Store 25 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatic merge on approval 3 | conditions: 4 | - "#approved-reviews-by>=1" 5 | # See https://doc.mergify.io/conditions.html#about-status-checks 6 | - "status-success=continuous-integration/travis-ci/pr" 7 | # See https://colineberhardt.github.io/cla-bot/#what-is-a-cla 8 | - "status-success=verification/cla-signed" 9 | actions: 10 | merge: 11 | method: merge 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | language: go 3 | go: 4 | - 1.14.x 5 | 6 | script: 7 | - travis_wait 40 make test-with-cover 8 | 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) 11 | 12 | deploy: 13 | provider: script 14 | script: ci/publish-docs-as-bot.sh 15 | on: 16 | branch: master 17 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 660 York Street, Suite 102, 6 | San Francisco, CA 94110 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | 12 | Developer's Certificate of Origin 1.1 13 | 14 | By making a contribution to this project, I certify that: 15 | 16 | (a) The contribution was created in whole or in part by me and I 17 | have the right to submit it under the open source license 18 | indicated in the file; or 19 | 20 | (b) The contribution is based upon previous work that, to the best 21 | of my knowledge, is covered under an appropriate open source 22 | license and I have the right under that license to submit that 23 | work with modifications, whether created in whole or in part 24 | by me, under the same open source license (unless I am 25 | permitted to submit under a different license), as indicated 26 | in the file; or 27 | 28 | (c) The contribution was provided directly to me by some other 29 | person who certified (a), (b) or (c) and I have not modified 30 | it. 31 | 32 | (d) I understand and agree that this project and the contribution 33 | are public and that a record of the contribution (including all 34 | personal information I submit with it, including my sign-off) is 35 | maintained indefinitely and may be redistributed consistent with 36 | this project or the open source license(s) involved. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CALICO_BUILD?=calico/go-build 2 | PACKAGE_NAME?=kubernetes-incubator/kube-aws 3 | LOCAL_USER_ID?=$(shell id -u $$USER) 4 | 5 | .PHONY: build 6 | build: 7 | ./build 8 | 9 | .PHONY: format 10 | format: 11 | test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" || \ 12 | test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -w {} + | tee /dev/stderr)" 13 | 14 | .PHONY: test 15 | test: build 16 | ./make/test 17 | 18 | .PHONY: test-with-cover 19 | test-with-cover: build 20 | ./make/test with-cover 21 | 22 | .PHONY: docs-dependencies 23 | docs-dependencies: 24 | if ! which gitbook; then npm install -g gitbook-cli; fi 25 | if ! which gh-pages; then npm install -g gh-pages; fi 26 | gitbook install 27 | 28 | .PHONY: generate-docs 29 | generate-docs: docs-dependencies 30 | gitbook build 31 | 32 | .PHONY: serve-docs 33 | serve-docs: docs-dependencies 34 | gitbook serve 35 | 36 | # For publishing to the `git remote` named `mumoshu` linked to the repo url `git@github.com:mumoshu/kube-aws.git`, the env vars should be: 37 | # REPO=mumoshu/kube-aws REMOTE=mumoshu make publish-docs 38 | # For publishing to the `git remote` named `origin` linked to the repo url `git@github.com:kubernetes-incubator/kube-aws.git`, the env vars should be: 39 | # REPO=kubernetes-incubator/kube-aws REMOTE=origin make publish-docs 40 | # Or just: 41 | # make publish-docs 42 | 43 | .PHONY: publish-docs 44 | publish-docs: REPO ?= kubernetes-incubator/kube-aws 45 | publish-docs: REMOTE ?= origin 46 | publish-docs: generate-docs 47 | NODE_DEBUG=gh-pages gh-pages -d _book -r git@github.com:$(REPO).git -o $(REMOTE) 48 | 49 | .PHONY: relnote 50 | relnote: 51 | go get golang.org/x/oauth2 52 | go get golang.org/x/net/context 53 | go get github.com/google/go-github/github 54 | go run hack/relnote.go 55 | 56 | .PHONY: merged-branches 57 | merged-branches: 58 | @git branch --merged | egrep -v "(^\*|master|v0.)" 59 | @bash -c "(echo 'pipe this into \`| xargs git branch -d\`' to delete) 1>&2" 60 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS file documentation: 2 | # https://github.com/kubernetes/community/blob/master/contributors/devel/owners.md 3 | 4 | approvers: 5 | - sig-cluster-lifecycle-leads 6 | - kube-aws-admins 7 | - kube-aws-maintainers 8 | 9 | reviewers: 10 | - kube-aws-maintainers 11 | - kube-aws-reviewers -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md 2 | 3 | aliases: 4 | sig-cluster-lifecycle-leads: 5 | - neolit123 6 | - justinsb 7 | - timothysc 8 | - fabriziopandini 9 | 10 | # ----------------------------------------------------------- 11 | # OWNER_ALIASES for Cluster API 12 | # ----------------------------------------------------------- 13 | 14 | # active folks who can be contacted to perform admin-related 15 | # tasks on the repo, or otherwise approve any PRS. 16 | kube-aws-admins: 17 | - dominicgunn 18 | - mumoshu 19 | 20 | # non-admin folks who have write-access and can approve any PRs in the repo 21 | kube-aws-maintainers: 22 | - dominicgunn 23 | - mumoshu 24 | 25 | # folks who can review and LGTM any PRs in the repo 26 | kube-aws-reviewers: 27 | - dominicgunn 28 | - mumoshu 29 | - calebamiles 30 | - colhom 31 | - philips 32 | - redbaron -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes on AWS (kube-aws) 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes-incubator/kube-aws)](https://goreportcard.com/report/github.com/kubernetes-incubator/kube-aws) 4 | [![Build Status](https://travis-ci.org/kubernetes-incubator/kube-aws.svg?branch=master)](https://travis-ci.org/kubernetes-incubator/kube-aws) 5 | [![License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](LICENSE) 6 | 7 | **Note**: The `master` branch may be in an *unstable or even broken state* during development. Please use [releases](https://github.com/kubernetes-incubator/kube-aws/releases) instead of the `master` branch in order to get stable binaries. 8 | 9 | `kube-aws` is a command-line tool to create/update/destroy Kubernetes clusters on AWS. 10 | 11 | ## Contributing 12 | 13 | This repository is about to enter read-only mode, and no further updates will be made here. 14 | 15 | If you would like to contribute, please find our fork located [here](https://github.com/kube-aws/kube-aws) 16 | 17 | Details of how to develop kube-aws are in our [Developer Guide](https://kubernetes-incubator.github.io/kube-aws/guides/developer-guide.html). 18 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | kube-aws is released once all the issues in a GitHub milestone are resolved. The process is as follows: 4 | 5 | 1. All the issues in the next release milestone is resolved 6 | 2. An OWNER writes a draft of a GitHub release 7 | 3. An OWNER runs `git tag -s $VERSION`, and then `./containerized-build-release-binaries/` to produce released binaries 8 | 4. The OWNER uploads the released binaries to the draft, and then pushes the tag with `git push $VERSION` 9 | 5. The release milestone is closed 10 | 6. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kube-aws $VERSION is released` 11 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Team to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | c-knowles 14 | camilb 15 | mumoshu 16 | redbaron 17 | -------------------------------------------------------------------------------- /awsconn/awsconn.go: -------------------------------------------------------------------------------- 1 | package awsconn 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/credentials" 8 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 11 | ) 12 | 13 | // NewSessionFromRegion creates an AWS session from AWS region and a debug flag 14 | func NewSessionFromRegion(region api.Region, debug bool, awsProfile string) (*session.Session, error) { 15 | awsConfig := aws.NewConfig(). 16 | WithRegion(region.String()). 17 | WithCredentialsChainVerboseErrors(true) 18 | 19 | if debug { 20 | awsConfig = awsConfig.WithLogLevel(aws.LogDebug) 21 | } 22 | 23 | if awsProfile != "" { 24 | awsConfig = awsConfig.WithCredentials(credentials.NewSharedCredentials("", awsProfile)) 25 | } 26 | 27 | session, err := newSession(awsConfig) 28 | if err != nil { 29 | return nil, fmt.Errorf("failed to establish aws session: %v", err) 30 | } 31 | return session, nil 32 | } 33 | 34 | // newSession returns an AWS session which supports source_profile and assume role with MFA 35 | // See #1231 for more details 36 | func newSession(config *aws.Config) (*session.Session, error) { 37 | return session.NewSessionWithOptions(session.Options{ 38 | Config: *config, 39 | // This seems to be required for AWS_SDK_LOAD_CONFIG 40 | SharedConfigState: session.SharedConfigEnable, 41 | // This seems to be required by MFA 42 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "sidebar": { 4 | } 5 | }, 6 | "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs"], 7 | "pluginsConfig": { 8 | "edit-link": { 9 | "base": "https://github.com/kubernetes-incubator/kube-aws/edit/master/docs", 10 | "label": "Edit This Page" 11 | }, 12 | "github": { 13 | "url": "https://github.com/kubernetes-incubator/kube-aws" 14 | } 15 | }, 16 | "root": "docs", 17 | "styles": { 18 | "website": "styles/site.css" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | COMMIT=$(git rev-parse HEAD) 5 | TAG=$(git describe --exact-match --abbrev=0 --tags "${COMMIT}" 2> /dev/null || true) 6 | BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | sed -e 's/[^a-zA-Z0-9+=._:/-]*//g' || true) 7 | OUTPUT_PATH=${OUTPUT_PATH:-"bin/kube-aws"} 8 | VERSION="" 9 | ETCD_VERSION="v3.4.9" 10 | KUBERNETES_VERSION="v1.16.10" 11 | 12 | if [ -z "$TAG" ]; then 13 | [[ -n "$BRANCH" ]] && VERSION="${BRANCH}/" 14 | VERSION="${VERSION}${COMMIT:0:8}" 15 | else 16 | VERSION=$TAG 17 | fi 18 | 19 | # check for changed files (not untracked files) 20 | if [ -n "$(git diff --shortstat 2> /dev/null | tail -n1)" ]; then 21 | VERSION="${VERSION}+dirty" 22 | fi 23 | 24 | echo "Building kube-aws ${VERSION} (default Kubernetes version ${KUBERNETES_VERSION})" 25 | 26 | printf 'Checking existence of the `packr` command that is used for embedding files into the resulting binary...' 27 | if ! which packr 1>/dev/null; then 28 | echo not found. installing... 29 | go get -u github.com/gobuffalo/packr/packr 30 | echo 'Installed `packr`.' 31 | else 32 | echo "found. Skipped installation." 33 | fi 34 | 35 | packr -vz 36 | 37 | if [[ ! "${BUILD_GOOS:-}" == "" ]];then 38 | export GOOS=$BUILD_GOOS 39 | fi 40 | if [[ ! "${BUILD_GOARCH:-}" == "" ]];then 41 | export GOARCH=$BUILD_GOARCH 42 | fi 43 | go build -ldflags "-X github.com/kubernetes-incubator/kube-aws/pkg/model.VERSION=${VERSION} -X github.com/kubernetes-incubator/kube-aws/pkg/api.KUBERNETES_VERSION=${KUBERNETES_VERSION} -X github.com/kubernetes-incubator/kube-aws/pkg/api.ETCD_VERSION=${ETCD_VERSION}" -a -tags netgo -installsuffix netgo -o "$OUTPUT_PATH" ./ 44 | -------------------------------------------------------------------------------- /build-release-binaries: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | 4 | # Creates kubernetes-aws-$os-$arch.tar.gz in the current directory 5 | # for every combination of $os and $arch 6 | 7 | # Add OS support here 8 | for os in linux darwin;do 9 | # Add architecture support here 10 | for arch in amd64;do 11 | output_folder="${os}-${arch}" 12 | rm -rf "$output_folder" 13 | mkdir "$output_folder" 14 | echo "Building kube-aws for GOOS=${os} GOARCH=${arch}" 15 | OUTPUT_PATH="$output_folder/kube-aws" BUILD_GOOS=$os BUILD_GOARCH=$arch ./build 16 | 17 | releaseTar=kube-aws-"${os}-${arch}".tar.gz 18 | rm -rf "$releaseTar" 19 | tar czvf $releaseTar "$output_folder" 20 | rm -rf "$output_folder" 21 | echo "${os}-${arch} --> ${releaseTar}" 22 | done 23 | done 24 | echo "Done!" 25 | -------------------------------------------------------------------------------- /builtin/files/etcdadm/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | test.env 3 | ssh-config 4 | Vagrantfile 5 | -------------------------------------------------------------------------------- /builtin/files/etcdadm/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test-integration 2 | test-integration: 3 | if ! [ -f Vagrantfile ]; then curl -o Vagrantfile https://raw.githubusercontent.com/coreos/coreos-vagrant/master/Vagrantfile; fi 4 | sed -i original 's/$update_channel = "alpha"/$update_channel = "stable"/' Vagrantfile 5 | if vagrant status | grep core-01 | grep running; then vagrant destroy -f; fi 6 | vagrant up 7 | vagrant ssh -c 'rm -f etcdadm test test.env' 8 | vagrant ssh-config > ssh-config 9 | scp -F ssh-config etcdadm test test.env core@core-01: 10 | vagrant ssh -c 'sudo ./test integration' 11 | -------------------------------------------------------------------------------- /builtin/files/kubeconfig.tmpl: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | clusters: 4 | - cluster: 5 | certificate-authority: credentials/ca.pem 6 | server: {{ .AdminAPIEndpointURL }} 7 | name: kube-aws-{{ .ClusterName }}-cluster 8 | contexts: 9 | - context: 10 | cluster: kube-aws-{{ .ClusterName }}-cluster 11 | namespace: default 12 | user: kube-aws-{{ .ClusterName }}-admin 13 | name: kube-aws-{{ .ClusterName }}-context 14 | users: 15 | - name: kube-aws-{{ .ClusterName }}-admin 16 | user: 17 | client-certificate: credentials/admin.pem 18 | client-key: credentials/admin-key.pem 19 | current-context: kube-aws-{{ .ClusterName }}-context 20 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/files/authentication-token-webhook-config.yaml: -------------------------------------------------------------------------------- 1 | # Generated by `aws-iam-authenticator init -i CLUSTER_NAME` and modified for templating 2 | clusters: 3 | - name: {{.Config.AWSIAMAuthenticatorClusterIDRef}} 4 | cluster: 5 | certificate-authority: /etc/kubernetes/ssl/ca.pem 6 | server: https://127.0.0.1:21362/authenticate 7 | # users refers to the API Server's webhook configuration 8 | # (we don't need to authenticate the API server). 9 | users: 10 | - name: apiserver 11 | # kubeconfig files require a context. Provide one for the API Server. 12 | current-context: webhook 13 | contexts: 14 | - name: webhook 15 | context: 16 | cluster: {{.Config.AWSIAMAuthenticatorClusterIDRef}} 17 | user: apiserver 18 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/files/controller-kubeconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | clusters: 4 | - cluster: 5 | server: {{.Config.APIEndpointURLPort}} 6 | certificate-authority: /etc/kubernetes/ssl/ca.pem 7 | name: kubernetes 8 | contexts: 9 | - context: 10 | cluster: kubernetes 11 | user: aws 12 | name: aws 13 | current-context: aws 14 | preferences: {} 15 | users: 16 | - name: aws 17 | user: 18 | exec: 19 | apiVersion: client.authentication.k8s.io/v1alpha1 20 | command: /opt/bin/aws-iam-authenticator 21 | args: 22 | - token 23 | - -i 24 | - {{.Config.AWSIAMAuthenticatorClusterIDRef}} 25 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/files/worker-kubeconfig.yaml: -------------------------------------------------------------------------------- 1 | kind: Config 2 | apiVersion: v1 3 | clusters: 4 | - cluster: 5 | server: {{.Config.APIEndpointURLPort}} 6 | certificate-authority: /etc/kubernetes/ssl/ca.pem 7 | name: kubernetes 8 | contexts: 9 | - context: 10 | cluster: kubernetes 11 | user: aws 12 | name: aws 13 | current-context: aws 14 | preferences: {} 15 | users: 16 | - name: aws 17 | user: 18 | exec: 19 | apiVersion: client.authentication.k8s.io/v1alpha1 20 | command: /opt/bin/aws-iam-authenticator 21 | args: 22 | - token 23 | - -i 24 | - {{.Config.AWSIAMAuthenticatorClusterIDRef}} 25 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/manifests/aws-auth-cm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-iam-authenticator 5 | namespace: kube-system 6 | data: 7 | config.yaml: | 8 | clusterID: {{ .Config.ClusterName }} 9 | server: 10 | mapRoles: 11 | {{ range $i, $role := .Config.IAMRoleARNs -}} 12 | - rolearn: {{$role}} 13 | username: system:node:{{`{{EC2PrivateDNSName}}`}} 14 | groups: 15 | - system:bootstrappers 16 | - system:nodes 17 | {{ end -}} 18 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/manifests/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ClusterRole 3 | apiVersion: rbac.authorization.k8s.io/v1beta1 4 | metadata: 5 | name: aws-iam-authenticator 6 | rules: 7 | - apiGroups: 8 | - iamauthenticator.k8s.aws 9 | resources: 10 | - iamidentitymappings 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - iamauthenticator.k8s.aws 17 | resources: 18 | - iamidentitymappings/status 19 | verbs: 20 | - patch 21 | - update 22 | - apiGroups: 23 | - "" 24 | resources: 25 | - events 26 | verbs: 27 | - create 28 | - update 29 | - patch 30 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/manifests/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ClusterRoleBinding 3 | apiVersion: rbac.authorization.k8s.io/v1beta1 4 | metadata: 5 | name: aws-iam-authenticator 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: aws-iam-authenticator 10 | subjects: 11 | - kind: ServiceAccount 12 | name: aws-iam-authenticator 13 | namespace: kube-system 14 | -------------------------------------------------------------------------------- /builtin/files/plugins/aws-iam-authenticator/manifests/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: aws-iam-authenticator 6 | namespace: kube-system 7 | 8 | -------------------------------------------------------------------------------- /builtin/files/plugins/cluster-autoscaler/manifests/pdb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: policy/v1beta1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | labels: 6 | app: cluster-autoscaler 7 | name: cluster-autoscaler 8 | namespace: kube-system 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: cluster-autoscaler 13 | minAvailable: 1 14 | -------------------------------------------------------------------------------- /builtin/files/plugins/cluster-autoscaler/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | app: cluster-autoscaler 7 | name: cluster-autoscaler 8 | namespace: kube-system 9 | spec: 10 | ports: 11 | - port: 8085 12 | protocol: TCP 13 | targetPort: 8085 14 | name: http 15 | selector: 16 | app: cluster-autoscaler 17 | type: "ClusterIP" 18 | -------------------------------------------------------------------------------- /builtin/files/plugins/cluster-autoscaler/manifests/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.prometheusMetrics.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: cluster-autoscaler 6 | namespace: {{ .Values.prometheusMetrics.namespace }} 7 | labels: 8 | app: cluster-autoscaler 9 | {{- range $key, $value := .Values.prometheusMetrics.selector }} 10 | {{ $key }}: {{ $value | quote }} 11 | {{- end }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: cluster-autoscaler 16 | endpoints: 17 | - port: http 18 | path: /metrics 19 | interval: {{ .Values.prometheusMetrics.interval }} 20 | namespaceSelector: 21 | any: true 22 | {{ end }} 23 | -------------------------------------------------------------------------------- /builtin/files/plugins/dashboard/manifests/metrics-scraper.yaml: -------------------------------------------------------------------------------- 1 | kind: Deployment 2 | apiVersion: apps/v1 3 | metadata: 4 | labels: 5 | k8s-app: dashboard-metrics-scraper 6 | name: dashboard-metrics-scraper 7 | namespace: {{.Values.namespace}} 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 10 11 | selector: 12 | matchLabels: 13 | k8s-app: dashboard-metrics-scraper 14 | template: 15 | metadata: 16 | labels: 17 | k8s-app: dashboard-metrics-scraper 18 | spec: 19 | containers: 20 | - name: dashboard-metrics-scraper 21 | image: {{.Values.metricsScraperImage}} 22 | ports: 23 | - containerPort: 8000 24 | protocol: TCP 25 | livenessProbe: 26 | httpGet: 27 | scheme: HTTP 28 | path: / 29 | port: 8000 30 | initialDelaySeconds: 30 31 | timeoutSeconds: 30 32 | volumeMounts: 33 | - mountPath: /tmp 34 | name: tmp-volume 35 | serviceAccountName: kubernetes-dashboard 36 | # Comment the following tolerations if Dashboard must not be deployed on master 37 | tolerations: 38 | - key: node-role.kubernetes.io/master 39 | effect: NoSchedule 40 | volumes: 41 | - name: tmp-volume 42 | emptyDir: {} 43 | --- 44 | kind: Service 45 | apiVersion: v1 46 | metadata: 47 | labels: 48 | k8s-app: dashboard-metrics-scraper 49 | name: dashboard-metrics-scraper 50 | namespace: {{.Values.namespace}} 51 | spec: 52 | ports: 53 | - port: 8000 54 | targetPort: 8000 55 | selector: 56 | k8s-app: dashboard-metrics-scraper 57 | -------------------------------------------------------------------------------- /builtin/files/plugins/dashboard/manifests/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | labels: 5 | app: kubernetes-dashboard 6 | name: kubernetes-dashboard-csrf 7 | namespace: {{.Values.namespace}} 8 | type: Opaque 9 | data: 10 | csrf: "" 11 | --- 12 | apiVersion: v1 13 | kind: Secret 14 | metadata: 15 | labels: 16 | k8s-app: kubernetes-dashboard 17 | name: kubernetes-dashboard-key-holder 18 | namespace: {{.Values.namespace}} 19 | type: Opaque 20 | {{ if .Values.tls.enabled -}} 21 | --- 22 | apiVersion: v1 23 | kind: Secret 24 | metadata: 25 | name: kubernetes-dashboard-certs 26 | namespace: {{ .Values.namespace }} 27 | type: kubernetes.io/tls 28 | data: 29 | tls.crt: "{{ if .Values.tls.certificates.supplied.certPEM }}{{ .Values.tls.certificates.supplied.certPEM | b64enc }}{{ else }}{{ insertTemplateFile .Values.tls.certificates.path.certPath . | b64enc }}{{ end }}" 30 | tls.key: "{{ if .Values.tls.certificates.supplied.keyPEM }}{{ .Values.tls.certificates.supplied.keyPEM | b64enc }}{{ else }}{{ insertTemplateFile .Values.tls.certificates.path.keyPath . | b64enc }}{{ end }}" 31 | ca.crt: "{{ if .Values.tls.certificates.supplied.caPEM }}{{ .Values.tls.certificates.supplied.caPEM | b64enc }}{{ else }}{{ insertTemplateFile .Values.tls.certificates.path.caPath . | b64enc }}{{ end }}" 32 | {{- end }} 33 | {{ if and .Values.ingress.enabled (not .Values.ingress.supressIngresSecret) -}} 34 | --- 35 | apiVersion: v1 36 | kind: Secret 37 | metadata: 38 | name: kubernetes-dashboard-ingress-certs 39 | namespace: {{ .Values.namespace }} 40 | type: kubernetes.io/tls 41 | data: 42 | tls.crt: "{{ if .Values.ingress.certificates.supplied.certPEM }}{{ .Values.ingress.certificates.supplied.certPEM | b64enc }}{{ else }}{{ insertTemplateFile .Values.ingress.certificates.path.certPath . | b64enc }}{{ end }}" 43 | tls.key: "{{ if .Values.ingress.certificates.supplied.keyPEM }}{{ .Values.ingress.certificates.supplied.keyPEM | b64enc }}{{ else }}{{ insertTemplateFile .Values.ingress.certificates.path.keyPath . | b64enc }}{{ end }}" 44 | ca.crt: "{{ if .Values.ingress.certificates.supplied.caPEM }}{{ .Values.ingress.certificates.supplied.caPEM | b64enc }}{{ else }}{{ insertTemplateFile .Values.ingress.certificates.path.caPath . | b64enc }}{{ end }}" 45 | {{- end }} 46 | -------------------------------------------------------------------------------- /builtin/files/plugins/kiam/manifests/agent-tls-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: kiam-agent-tls 5 | namespace: kube-system 6 | type: kubernetes.io/tls 7 | data: 8 | tls.crt: {{ insertTemplateFile "credentials/kiam-agent.pem" . | b64enc }} 9 | tls.key: {{ insertTemplateFile "credentials/kiam-agent-key.pem" . | b64enc }} 10 | ca.crt: {{ insertTemplateFile "credentials/kiam-ca.pem" . | b64enc }} -------------------------------------------------------------------------------- /builtin/files/plugins/kiam/manifests/server-cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: kiam-server 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: kiam-server 9 | subjects: 10 | - kind: ServiceAccount 11 | name: kiam-server 12 | namespace: kube-system -------------------------------------------------------------------------------- /builtin/files/plugins/kiam/manifests/server-cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: kiam-server 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - namespaces 10 | - pods 11 | verbs: 12 | - watch 13 | - get 14 | - list 15 | - apiGroups: 16 | - "" 17 | resources: 18 | - events 19 | verbs: 20 | - create 21 | - patch -------------------------------------------------------------------------------- /builtin/files/plugins/kiam/manifests/server-tls-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: kiam-server-tls 5 | namespace: kube-system 6 | type: kubernetes.io/tls 7 | data: 8 | tls.crt: {{ insertTemplateFile "credentials/kiam-server.pem" . | b64enc }} 9 | tls.key: {{ insertTemplateFile "credentials/kiam-server-key.pem" . | b64enc }} 10 | ca.crt: {{ insertTemplateFile "credentials/kiam-ca.pem" . | b64enc }} -------------------------------------------------------------------------------- /builtin/files/plugins/kiam/manifests/service-account.yaml: -------------------------------------------------------------------------------- 1 | kind: ServiceAccount 2 | apiVersion: v1 3 | metadata: 4 | name: kiam-server 5 | namespace: kube-system -------------------------------------------------------------------------------- /builtin/files/plugins/kiam/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kiam-server 5 | namespace: kube-system 6 | spec: 7 | clusterIP: None 8 | selector: 9 | app: kiam 10 | role: server 11 | ports: 12 | - name: {{ .Values.server.portName }} 13 | port: 443 14 | targetPort: 443 15 | protocol: TCP -------------------------------------------------------------------------------- /builtin/files/plugins/kube2iam/manifests/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: kube2iam 5 | namespace: kube-system 6 | labels: 7 | app: kube2iam 8 | annotations: 9 | scheduler.alpha.kubernetes.io/critical-pod: '' 10 | spec: 11 | updateStrategy: 12 | rollingUpdate: 13 | maxUnavailable: 100% 14 | type: RollingUpdate 15 | selector: 16 | matchLabels: 17 | name: kube2iam 18 | template: 19 | metadata: 20 | labels: 21 | name: kube2iam 22 | spec: 23 | priorityClassName: system-cluster-critical 24 | serviceAccountName: kube2iam 25 | hostNetwork: true 26 | tolerations: 27 | - operator: Exists 28 | effect: NoSchedule 29 | - operator: Exists 30 | effect: NoExecute 31 | - operator: Exists 32 | key: CriticalAddonsOnly 33 | containers: 34 | - image: {{ .Values.image }} 35 | name: kube2iam 36 | args: 37 | - "--app-port=8282" 38 | - "--auto-discover-base-arn" 39 | - "--auto-discover-default-role" 40 | - "--iptables=true" 41 | - "--host-ip=$(HOST_IP)" 42 | {{- if .Config.Kubernetes.Networking.AmazonVPC.Enabled }} 43 | - "--host-interface=!eni0" 44 | {{- else if eq .Config.Kubernetes.Networking.SelfHosting.Type "canal" }} 45 | - "--host-interface=cali+" 46 | {{- else }} 47 | - "--host-interface=cni0" 48 | {{- end }} 49 | env: 50 | - name: HOST_IP 51 | valueFrom: 52 | fieldRef: 53 | fieldPath: status.podIP 54 | ports: 55 | - containerPort: 8282 56 | hostPort: 8282 57 | name: http 58 | resources: 59 | limits: 60 | memory: 128Mi 61 | requests: 62 | cpu: 100m 63 | memory: 64Mi 64 | securityContext: 65 | privileged: true 66 | -------------------------------------------------------------------------------- /builtin/files/plugins/kube2iam/manifests/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: kube2iam 5 | namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | annotations: 11 | rbac.authorization.kubernetes.io/autoupdate: "true" 12 | labels: 13 | kubernetes.io/bootstrapping: kube2iam 14 | name: kube2iam 15 | rules: 16 | - apiGroups: 17 | - "" 18 | resources: 19 | - pods 20 | - namespaces 21 | verbs: 22 | - get 23 | - list 24 | - watch 25 | --- 26 | apiVersion: rbac.authorization.k8s.io/v1 27 | kind: ClusterRoleBinding 28 | metadata: 29 | name: kube2iam 30 | roleRef: 31 | apiGroup: rbac.authorization.k8s.io 32 | kind: ClusterRole 33 | name: kube2iam 34 | subjects: 35 | - kind: ServiceAccount 36 | name: kube2iam 37 | namespace: kube-system -------------------------------------------------------------------------------- /builtin/files/plugins/kube2iam/plugin.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | name: kube2iam 3 | version: 0.1.0 4 | spec: 5 | cluster: 6 | values: 7 | image: jtblin/kube2iam:0.10.8 8 | 9 | kubernetes: 10 | manifests: 11 | - source: 12 | path: manifests/daemonset.yaml 13 | - source: 14 | path: manifests/rbac.yaml 15 | 16 | cloudformation: 17 | stacks: 18 | controlPlane: 19 | resources: 20 | content: | 21 | { 22 | "IAMManagedPolicyControllerKube2IAM" : { 23 | "Type" : "AWS::IAM::ManagedPolicy", 24 | "Properties" : { 25 | "Description" : "Policy for managing Kube2IAM on kube-aws controllers", 26 | "Path" : "/", 27 | "Roles":[ { "Ref" : "IAMRoleController" } ], 28 | "PolicyDocument" : { 29 | "Version":"2012-10-17", 30 | "Statement": [ 31 | { 32 | "Action": "sts:AssumeRole", 33 | "Effect":"Allow", 34 | "Resource":"*" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | } 41 | nodePool: 42 | resources: 43 | content: | 44 | { 45 | "IAMManagedPolicyWorkerKube2IAM" : { 46 | "Type" : "AWS::IAM::ManagedPolicy", 47 | "Properties" : { 48 | "Description" : "Policy for managing Kube2IAM on kube-aws nodepools", 49 | "Path" : "/", 50 | "Roles":[ { "Ref" : "IAMRoleWorker" } ], 51 | "PolicyDocument" : { 52 | "Version":"2012-10-17", 53 | "Statement": [ 54 | { 55 | "Action": "sts:AssumeRole", 56 | "Effect":"Allow", 57 | "Resource":"*" 58 | } 59 | ] 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /builtin/files/plugins/upgrade-helper/assets/upgrade-helper-post.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Restore webhooks that were exported and then deleted by upgrade-helper.sh 3 | 4 | retries=5 5 | hyperkube_image="{{ .Config.HyperkubeImage.RepoWithTag }}" 6 | webhooks_save_path="/srv/kubernetes" 7 | disable_webhooks="{{ .Values.disableWebhooks }}" 8 | disable_worker_communication_check="{{ .Values.disableWorkerCommunicationChecks }}" 9 | 10 | kubectl() { 11 | /usr/bin/docker run -i --rm -v /etc/kubernetes:/etc/kubernetes:ro -v ${webhooks_save_path}:${webhooks_save_path}:rw --net=host ${hyperkube_image} /hyperkube kubectl --kubeconfig=/etc/kubernetes/kubeconfig/admin.yaml "$@" 12 | } 13 | 14 | applyall() { 15 | kubectl apply --force -f $(echo "$@" | tr ' ' ',') 16 | } 17 | 18 | restore_webhooks() { 19 | local type=$1 20 | local file=$2 21 | 22 | if [[ -s "${file}.index" ]]; then 23 | echo "Restoring all ${type} webhooks from ${file}.index" 24 | hooks=$(cat "${file}.index") 25 | for h in ${hooks}; do 26 | echo "restoring ${type} webhook ${h}..." 27 | exists=$(kubectl get ${type}webhookconfiguration ${h} --no-headers --ignore-not-found) 28 | if [[ -n "${exists}" ]]; then 29 | echo "${h} found - not restoring!" 30 | else 31 | if [[ -s "${file}.${type}.${h}.yaml" ]]; then 32 | echo "restoring from ${file}.${type}.${h}.yaml..." 33 | applyall ${file}.${type}.${h}.yaml 34 | else 35 | echo "error! file ${file}.${type}.${h}.yaml not found or is empty" 36 | fi 37 | fi 38 | done 39 | else 40 | echo "no webhooks to restore in $file" 41 | fi 42 | } 43 | 44 | if [[ "${disable_webhooks}" == "true" ]]; then 45 | echo "Restoring all validating and mutating webhooks..." 46 | restore_webhooks validating ${webhooks_save_path}/validating_webhooks 47 | restore_webhooks mutating ${webhooks_save_path}/mutating_webhooks 48 | fi 49 | 50 | if [[ "${disable_worker_communication_check}" == "true" ]]; then 51 | echo "Removing the worker communication check from cfn-signal service..." 52 | cat >/opt/bin/check-worker-communication < 64 { 11 | return fmt.Errorf("IAM role name(=%s) will be %d characters long. It exceeds the AWS limit of 64 characters", name, len(name)) 12 | } 13 | } else { 14 | name := fmt.Sprintf("%s-%s-PRK1CVQNY7XZ-%s-%s", clusterName, nestedStackLogicalName, region, managedIAMRoleName) 15 | if len(name) > 64 { 16 | limit := 64 - len(name) + len(clusterName) + len(nestedStackLogicalName) + len(managedIAMRoleName) 17 | return fmt.Errorf("IAM role name(=%s) will be %d characters long. It exceeds the AWS limit of 64 characters: cluster name(=%s) + nested stack name(=%s) + managed iam role name(=%s) should be less than or equal to %d", name, len(name), clusterName, nestedStackLogicalName, managedIAMRoleName, limit) 18 | } 19 | } 20 | return nil 21 | } 22 | 23 | func ValidateStableRoleNameLength(clusterName string, managedIAMRoleName string, region string, strict bool) error { 24 | // include cluster name in the managed role 25 | // enables multiple clusters in the same account and region to have mirrored configuration without clashes 26 | if strict { 27 | name := managedIAMRoleName 28 | if len(name) > 64 { 29 | return fmt.Errorf("IAM role name(=%s) will be %d characters long. It exceeds the AWS limit of 64 characters", name, len(name)) 30 | } 31 | } else { 32 | name := fmt.Sprintf("%s-%s-%s", clusterName, region, managedIAMRoleName) 33 | if len(name) > 64 { 34 | limit := 64 - len(name) + len(managedIAMRoleName) 35 | return fmt.Errorf("IAM role name(=%s) will be %d characters long. It exceeds the AWS limit of 64 characters: clusterName(=%s) + region name(=%s) + managed iam role name(=%s) should be less than or equal to %d", name, len(name), clusterName, region, managedIAMRoleName, limit) 36 | } 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /cfnresource/naming_test.go: -------------------------------------------------------------------------------- 1 | package cfnresource 2 | 3 | import "testing" 4 | 5 | func TestValidateRoleNameLength(t *testing.T) { 6 | t.Run("WhenMax", func(t *testing.T) { 7 | if e := ValidateUnstableRoleNameLength("my-firstcluster", "prodWorkerks", "prod-workers", "us-east-1", false); e != nil { 8 | t.Errorf("expected validation to succeed but failed: %v", e) 9 | } 10 | }) 11 | t.Run("WhenTooLong", func(t *testing.T) { 12 | if e := ValidateUnstableRoleNameLength("my-secondcluster", "prodWorkerks", "prod-workers", "us-east-1", false); e == nil { 13 | t.Error("expected validation to fail but succeeded") 14 | } 15 | }) 16 | } 17 | 18 | func TestValidateManagedRoleNameLength(t *testing.T) { 19 | t.Run("WhenMax", func(t *testing.T) { 20 | if e := ValidateStableRoleNameLength("prod", "workers", "ap-southeast-1", false); e != nil { 21 | t.Errorf("expected validation to succeed but failed: %v", e) 22 | } 23 | }) 24 | t.Run("WhenTooLong", func(t *testing.T) { 25 | if e := ValidateStableRoleNameLength("prod", "workers-role-with-very-very-very-very-very-long-name", "ap-southeast-1", false); e == nil { 26 | t.Error("expected validation to fail but succeeded") 27 | } 28 | }) 29 | } 30 | 31 | func TestValidateManagedRoleStrictNameLength(t *testing.T) { 32 | t.Run("WhenMax", func(t *testing.T) { 33 | if e := ValidateStableRoleNameLength("prod", "workers-role-with-very-very-very-very-very-long-name", "ap-southeast-1", true); e != nil { 34 | t.Errorf("expected validation to succeed but failed: %v", e) 35 | } 36 | }) 37 | t.Run("WhenTooLong", func(t *testing.T) { 38 | if e := ValidateStableRoleNameLength("prod", "workers-role-with-very-very-very-very-very-long-name-very-very-very-very-very-long-name", "ap-southeast-1", true); e == nil { 39 | t.Error("expected validation to fail but succeeded") 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /cfnstack/ec2.go: -------------------------------------------------------------------------------- 1 | package cfnstack 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/service/ec2" 5 | ) 6 | 7 | type EC2Interrogator interface { 8 | DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) 9 | } 10 | -------------------------------------------------------------------------------- /cfnstack/provisioner_test.go: -------------------------------------------------------------------------------- 1 | package cfnstack 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/aws/aws-sdk-go/service/s3" 7 | ) 8 | 9 | type dummyS3ObjectPutterService struct { 10 | ExpectedBucket string 11 | ExpectedKey string 12 | ExpectedBody string 13 | ExpectedContentType string 14 | ExpectedContentLength int64 15 | } 16 | 17 | func (s3Svc dummyS3ObjectPutterService) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) { 18 | 19 | if s3Svc.ExpectedContentLength != *input.ContentLength { 20 | return nil, fmt.Errorf( 21 | "expected content length does not match supplied content length\nexpected=%v, supplied=%v", 22 | s3Svc.ExpectedContentLength, 23 | input.ContentLength, 24 | ) 25 | } 26 | 27 | if s3Svc.ExpectedBucket != *input.Bucket { 28 | return nil, fmt.Errorf( 29 | "expected bucket does not match supplied bucket\nexpected=%v, supplied=%v", 30 | s3Svc.ExpectedBucket, 31 | input.Bucket, 32 | ) 33 | } 34 | 35 | if s3Svc.ExpectedKey != *input.Key { 36 | return nil, fmt.Errorf( 37 | "expected key does not match supplied key\nexpected=%v, supplied=%v", 38 | s3Svc.ExpectedKey, 39 | *input.Key, 40 | ) 41 | } 42 | 43 | if s3Svc.ExpectedContentType != *input.ContentType { 44 | return nil, fmt.Errorf( 45 | "expected content type does not match supplied content type\nexpected=%v, supplied=%v", 46 | s3Svc.ExpectedContentType, 47 | input.ContentType, 48 | ) 49 | } 50 | 51 | buf := new(bytes.Buffer) 52 | buf.ReadFrom(input.Body) 53 | suppliedBody := buf.String() 54 | 55 | if s3Svc.ExpectedBody != suppliedBody { 56 | return nil, fmt.Errorf( 57 | "expected body does not match supplied body\nexpected=%v, supplied=%v", 58 | s3Svc.ExpectedBody, 59 | suppliedBody, 60 | ) 61 | } 62 | 63 | resp := &s3.PutObjectOutput{} 64 | 65 | return resp, nil 66 | } 67 | -------------------------------------------------------------------------------- /cfnstack/s3_uri.go: -------------------------------------------------------------------------------- 1 | package cfnstack 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | type S3URI interface { 10 | Bucket() string 11 | KeyComponents() []string 12 | BucketAndKey() string 13 | String() string 14 | } 15 | 16 | type s3URIImpl struct { 17 | bucket string 18 | directory string 19 | } 20 | 21 | func (u s3URIImpl) Bucket() string { 22 | return u.bucket 23 | } 24 | 25 | func (u s3URIImpl) KeyComponents() []string { 26 | if u.directory != "" { 27 | return []string{ 28 | u.directory, 29 | } 30 | } 31 | return []string{} 32 | } 33 | 34 | func (u s3URIImpl) BucketAndKey() string { 35 | components := []string{} 36 | path := u.KeyComponents() 37 | components = append(components, u.bucket) 38 | components = append(components, path...) 39 | return strings.Join(components, "/") 40 | } 41 | 42 | func (u s3URIImpl) String() string { 43 | return fmt.Sprintf("s3://%s", u.BucketAndKey()) 44 | } 45 | 46 | func S3URIFromString(s3URI string) (S3URI, error) { 47 | re := regexp.MustCompile("s3://(?P[^/]+)/(?P.+[^/])/*$") 48 | matches := re.FindStringSubmatch(s3URI) 49 | var bucket string 50 | var directory string 51 | if len(matches) == 3 { 52 | bucket = matches[1] 53 | directory = matches[2] 54 | } else { 55 | re := regexp.MustCompile("s3://(?P[^/]+)/*$") 56 | matches := re.FindStringSubmatch(s3URI) 57 | 58 | if len(matches) == 2 { 59 | bucket = matches[1] 60 | } else { 61 | return nil, fmt.Errorf("failed to parse s3 uri(=%s): The valid uri pattern for it is s3://mybucket/mydir or s3://mybucket", s3URI) 62 | } 63 | } 64 | return s3URIImpl{ 65 | bucket: bucket, 66 | directory: directory, 67 | }, nil 68 | } 69 | -------------------------------------------------------------------------------- /ci/.gitignore: -------------------------------------------------------------------------------- 1 | kube-aws-bot-git-ssh-key* 2 | -------------------------------------------------------------------------------- /ci/publish-docs-as-bot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ve 4 | 5 | # This script: 6 | # - assumes you've created a ssh key used by kube-aws-bot to ssh into github: 7 | # https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/ 8 | # - utilizes Travis CI's file encryption feature for encrypting ssh keys 9 | 10 | # It requires the following command beforehand: 11 | # $ gem install travis 12 | # $ travis login --auto 13 | # $ travis encrypt-file ci/kube-aws-bot-git-ssh-key --repo /kube-aws 14 | 15 | # And then change this line to the one output from the `travis encrypt-file` command above 16 | openssl aes-256-cbc -K $encrypted_514cf8442810_key -iv $encrypted_514cf8442810_iv -in kube-aws-bot-git-ssh-key.enc -out ci/kube-aws-bot-git-ssh-key -d 17 | 18 | ## Prevent the following error: 19 | ## Permissions 0644 for '/home/travis/gopath/src/github.com/kubernetes-incubator/kube-aws/ci/kube-aws-bot-git-ssh-key' are too open. 20 | ## ... 21 | ## bad permissions: ignore key: /home/travis/gopath/src/github.com/kubernetes-incubator/kube-aws/ci/kube-aws-bot-git-ssh-key 22 | chmod 600 ci/kube-aws-bot-git-ssh-key 23 | 24 | # Finally run the following command to add the encrypted key to the git repo: 25 | # $ git add kube-aws-bot-git-ssh-key.enc 26 | # $ $ git commit -m '...' 27 | 28 | echo -e "Host github.com\n\tStrictHostKeyChecking no\nIdentityFile $(pwd)/ci/kube-aws-bot-git-ssh-key\n" >> ~/.ssh/config 29 | 30 | set +e 31 | ssh git@github.com 32 | status=$? 33 | set -e 34 | 35 | if [ $status -ne 1 ]; then 36 | echo ssh connection check to github failed: ssh command exited with status $status 1>&2 37 | exit 1 38 | fi 39 | 40 | echo Node.js $(node -v) is installed/being used 41 | 42 | REPO=$TRAVIS_REPO_SLUG make publish-docs 43 | -------------------------------------------------------------------------------- /cmd/calculator.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/kubernetes-incubator/kube-aws/core/root" 8 | "github.com/kubernetes-incubator/kube-aws/logger" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | //TODO this is a first step to calculate the stack cost 13 | //this command could scrap aws to print the total cost, rather just showing the link 14 | 15 | var ( 16 | cmdCalculator = &cobra.Command{ 17 | Use: "calculator", 18 | Short: "Discover the monthly cost of your cluster", 19 | Long: ``, 20 | RunE: runCmdCalculator, 21 | SilenceUsage: true, 22 | } 23 | 24 | calculatorOpts = struct { 25 | profile string 26 | awsDebug bool 27 | }{} 28 | ) 29 | 30 | func init() { 31 | RootCmd.AddCommand(cmdCalculator) 32 | cmdCalculator.Flags().StringVar(&calculatorOpts.profile, "profile", "", "The AWS profile to use from credentials file") 33 | cmdCalculator.Flags().BoolVar(&calculatorOpts.awsDebug, "aws-debug", false, "Log debug information from aws-sdk-go library") 34 | } 35 | 36 | func runCmdCalculator(_ *cobra.Command, _ []string) error { 37 | 38 | opts := root.NewOptions(false, false, calculatorOpts.profile) 39 | 40 | cluster, err := root.LoadClusterFromFile(configPath, opts, calculatorOpts.awsDebug) 41 | if err != nil { 42 | return fmt.Errorf("failed to initialize cluster driver: %v", err) 43 | } 44 | 45 | if _, err := cluster.ValidateStack(); err != nil { 46 | return fmt.Errorf("error validating cluster: %v", err) 47 | } 48 | 49 | urls, err := cluster.EstimateCost() 50 | 51 | if err != nil { 52 | return fmt.Errorf("%v", err) 53 | } 54 | 55 | logger.Heading("To estimate your monthly cost, open the links below") 56 | logger.Infof("%v", strings.Join(urls, "\n")) 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd/destroy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/kubernetes-incubator/kube-aws/core/root" 12 | "github.com/kubernetes-incubator/kube-aws/logger" 13 | ) 14 | 15 | var ( 16 | cmdDestroy = &cobra.Command{ 17 | Use: "destroy", 18 | Short: "Destroy an existing Kubernetes cluster", 19 | Long: ``, 20 | RunE: runCmdDestroy, 21 | SilenceUsage: true, 22 | } 23 | destroyOpts = root.DestroyOptions{} 24 | ) 25 | 26 | func init() { 27 | RootCmd.AddCommand(cmdDestroy) 28 | cmdDestroy.Flags().StringVar(&destroyOpts.Profile, "profile", "", "The AWS profile to use from credentials file") 29 | cmdDestroy.Flags().BoolVar(&destroyOpts.AwsDebug, "aws-debug", false, "Log debug information from aws-sdk-go library") 30 | cmdDestroy.Flags().BoolVar(&destroyOpts.Force, "force", false, "Don't ask for confirmation") 31 | } 32 | 33 | func runCmdDestroy(_ *cobra.Command, _ []string) error { 34 | if !destroyOpts.Force && !destroyConfirmation() { 35 | logger.Info("Operation Cancelled") 36 | return nil 37 | } 38 | 39 | c, err := root.ClusterDestroyerFromFile(configPath, destroyOpts) 40 | if err != nil { 41 | return fmt.Errorf("error parsing config: %v", err) 42 | } 43 | 44 | if err := c.Destroy(); err != nil { 45 | return fmt.Errorf("failed destroying cluster: %v", err) 46 | } 47 | 48 | logger.Info("CloudFormation stack is being destroyed. This will take several minutes") 49 | return nil 50 | } 51 | 52 | func destroyConfirmation() bool { 53 | reader := bufio.NewReader(os.Stdin) 54 | fmt.Print("This operation will destroy the cluster. Are you sure? [y,n]: ") 55 | text, _ := reader.ReadString('\n') 56 | text = strings.TrimSuffix(strings.ToLower(text), "\n") 57 | 58 | return text == "y" || text == "yes" 59 | } 60 | -------------------------------------------------------------------------------- /cmd/flag_util.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type flag struct { 10 | name string 11 | val string 12 | } 13 | 14 | func validateRequired(required ...flag) error { 15 | var missing []string 16 | for _, req := range required { 17 | if req.val == "" { 18 | missing = append(missing, strconv.Quote(req.name)) 19 | } 20 | } 21 | if len(missing) != 0 { 22 | return fmt.Errorf("missing required flag(s): %s", strings.Join(missing, ", ")) 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/kubernetes-incubator/kube-aws/logger" 5 | "github.com/mgutz/ansi" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | RootCmd = &cobra.Command{ 11 | Use: "kube-aws", 12 | Short: "Manage Kubernetes clusters on AWS", 13 | Long: ``, 14 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 15 | colorEnabled, err := cmd.Flags().GetBool("color") 16 | if err != nil { 17 | panic(err) 18 | } 19 | ansi.DisableColors(!colorEnabled) 20 | }, 21 | } 22 | 23 | configPath = "cluster.yaml" 24 | ) 25 | 26 | func init() { 27 | RootCmd.SetOutput(logger.Writer(logger.StdErrOutput)) 28 | RootCmd.PersistentFlags().BoolVarP( 29 | &logger.Silent, 30 | "silent", 31 | "s", 32 | false, 33 | "do not show messages", 34 | ) 35 | RootCmd.PersistentFlags().BoolVarP( 36 | &logger.Verbose, 37 | "verbose", 38 | "v", 39 | false, 40 | "show debug messages", 41 | ) 42 | RootCmd.PersistentFlags().BoolVarP( 43 | &logger.Color, 44 | "color", 45 | "", 46 | false, 47 | "use color for messages", 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /cmd/show.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/kubernetes-incubator/kube-aws/core/root" 5 | "github.com/kubernetes-incubator/kube-aws/logger" 6 | "github.com/kubernetes-incubator/kube-aws/pki" 7 | "github.com/spf13/cobra" 8 | "sort" 9 | ) 10 | 11 | var ( 12 | cmdShow = &cobra.Command{ 13 | Use: "show", 14 | Short: "Show info about certificates in credentials directory", 15 | Long: ``, 16 | SilenceUsage: true, 17 | } 18 | 19 | cmdShowCertificates = &cobra.Command{ 20 | Use: "certificates", 21 | Short: "Show info about certificates", 22 | Long: `Loads all certificates from credentials directory and prints certificate 23 | Issuer, Validity, Subject and DNS Names fields`, 24 | RunE: runCmdShowCertificates, 25 | SilenceUsage: true, 26 | } 27 | ) 28 | 29 | func init() { 30 | RootCmd.AddCommand(cmdShow) 31 | cmdShow.AddCommand(cmdShowCertificates) 32 | } 33 | 34 | func runCmdShowCertificates(_ *cobra.Command, _ []string) error { 35 | certs, err := root.LoadCertificates() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | keys := sortedKeys(certs) 41 | for _, k := range keys { 42 | cert := certs[k] 43 | logger.Headingf("--- %s ---\n", k) 44 | for _, v := range cert { 45 | logger.Info(v) 46 | } 47 | logger.Info("") 48 | } 49 | return nil 50 | } 51 | 52 | func sortedKeys(m map[string]pki.Certificates) []string { 53 | 54 | var keys []string 55 | for k := range m { 56 | keys = append(keys, k) 57 | } 58 | 59 | sort.Sort(sort.StringSlice(keys)) 60 | return keys 61 | } 62 | -------------------------------------------------------------------------------- /cmd/status.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kubernetes-incubator/kube-aws/core/root" 7 | "github.com/kubernetes-incubator/kube-aws/logger" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | cmdStatus = &cobra.Command{ 13 | Use: "status", 14 | Short: "Describe an existing Kubernetes cluster", 15 | Long: ``, 16 | RunE: runCmdStatus, 17 | SilenceUsage: true, 18 | } 19 | 20 | statusOpts = struct { 21 | profile string 22 | }{} 23 | ) 24 | 25 | func init() { 26 | RootCmd.AddCommand(cmdStatus) 27 | cmdStatus.Flags().StringVar(&statusOpts.profile, "profile", "", "The AWS profile to use from credentials file") 28 | } 29 | 30 | func runCmdStatus(_ *cobra.Command, _ []string) error { 31 | opts := root.NewOptions(false, false, statusOpts.profile) 32 | describer, err := root.ClusterDescriberFromFile(configPath, opts) 33 | if err != nil { 34 | return fmt.Errorf("failed to read cluster config: %v", err) 35 | } 36 | 37 | info, err := describer.Info() 38 | if err != nil { 39 | return fmt.Errorf("failed fetching cluster info: %v", err) 40 | } 41 | 42 | logger.Info(info) 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /cmd/validate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kubernetes-incubator/kube-aws/core/root" 7 | "github.com/kubernetes-incubator/kube-aws/logger" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | cmdValidate = &cobra.Command{ 13 | Use: "validate", 14 | Short: "Validate cluster assets", 15 | Long: ``, 16 | RunE: runCmdValidate, 17 | SilenceUsage: true, 18 | } 19 | 20 | validateOpts = struct { 21 | awsDebug, skipWait bool 22 | profile string 23 | targets []string 24 | }{} 25 | ) 26 | 27 | func init() { 28 | RootCmd.AddCommand(cmdValidate) 29 | cmdValidate.Flags().BoolVar( 30 | &validateOpts.awsDebug, 31 | "aws-debug", 32 | false, 33 | "Log debug information from aws-sdk-go library", 34 | ) 35 | cmdValidate.Flags().StringVar(&validateOpts.profile, "profile", "", "The AWS profile to use from credentials file") 36 | cmdValidate.Flags().StringSliceVar( 37 | &validateOpts.targets, 38 | "targets", 39 | root.AllOperationTargetsAsStringSlice(), 40 | "Validate nothing but specified sub-stacks. Specify `all` or any combination of `etcd`, `control-plane`, and node pool names. Defaults to `all`") 41 | } 42 | 43 | func runCmdValidate(_ *cobra.Command, _ []string) error { 44 | opts := root.NewOptions(validateOpts.awsDebug, validateOpts.skipWait, validateOpts.profile) 45 | 46 | cluster, err := root.LoadClusterFromFile(configPath, opts, validateOpts.awsDebug) 47 | if err != nil { 48 | return fmt.Errorf("failed to initialize cluster driver: %v", err) 49 | } 50 | 51 | logger.Info("Validating UserData and stack template...\n") 52 | 53 | targets := root.OperationTargetsFromStringSlice(validateOpts.targets) 54 | 55 | report, err := cluster.ValidateStack(targets) 56 | if report != "" { 57 | logger.Infof("Validation Report: %s\n", report) 58 | } 59 | if err != nil { 60 | return err 61 | } 62 | 63 | logger.Info("stack template is valid.\n\n") 64 | logger.Info("Validation OK!") 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/kubernetes-incubator/kube-aws/logger" 5 | "github.com/kubernetes-incubator/kube-aws/pkg/model" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | cmdVersion = &cobra.Command{ 11 | Use: "version", 12 | Short: "Print version information and exit", 13 | Long: ``, 14 | Run: runCmdVersion, 15 | } 16 | ) 17 | 18 | func init() { 19 | RootCmd.AddCommand(cmdVersion) 20 | } 21 | 22 | func runCmdVersion(_ *cobra.Command, _ []string) { 23 | logger.Infof("kube-aws version %s\n", model.VERSION) 24 | } 25 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /containerized-build-release-binaries: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | COREOS_KUBERNETES_ROOT=$(git rev-parse --show-toplevel) 5 | KUBERNETES_MOUNT_DIR="/go/src/github.com/kubernetes-incubator/kube-aws" 6 | 7 | docker run --rm -i \ 8 | -v "${COREOS_KUBERNETES_ROOT}:${KUBERNETES_MOUNT_DIR}:z" -w ${KUBERNETES_MOUNT_DIR} \ 9 | --entrypoint /bin/bash golang:1.10.0 <" 9 | echo " example: $0 'v1.11.3'" 10 | exit 1 11 | fi 12 | 13 | # the version needs to be published, see https://quay.io/repository/coreos/hyperkube?tag=latest&tab=tags 14 | CURRENT_VERSION=${CURRENT_VERSION:-"v1.11.3"} 15 | TARGET_VERSION=${1} 16 | 17 | CURRENT_VERSION_BASE=${CURRENT_VERSION%%_*} 18 | TARGET_VERSION_BASE=${TARGET_VERSION%%_*} 19 | 20 | CURRENT_VERSION_SEMVER=${CURRENT_VERSION/_/+} 21 | TARGET_VERSION_SEMVER=${TARGET_VERSION/_/+} 22 | 23 | GIT_ROOT=$(git rev-parse --show-toplevel) 24 | 25 | cd $GIT_ROOT 26 | TRACKED=($(git grep -F "${CURRENT_VERSION_BASE}"| awk -F : '{print $1}' | sort -u)) 27 | for i in "${TRACKED[@]}"; do 28 | echo Updating $i 29 | if [ "$(uname -s)" == "Darwin" ]; then 30 | sed -i "" "s/${CURRENT_VERSION}/${TARGET_VERSION}/g" $i 31 | sed -i "" "s/${CURRENT_VERSION_SEMVER}/${TARGET_VERSION_SEMVER}/g" $i 32 | sed -i "" "s/${CURRENT_VERSION_BASE}/${TARGET_VERSION_BASE}/g" $i 33 | else 34 | sed -i "s/${CURRENT_VERSION}/${TARGET_VERSION}/g" $i 35 | sed -i "s/${CURRENT_VERSION_SEMVER}/${TARGET_VERSION_SEMVER}/g" $i 36 | sed -i "s/${CURRENT_VERSION_BASE}/${TARGET_VERSION_BASE}/g" $i 37 | fi 38 | done 39 | -------------------------------------------------------------------------------- /contrib/cluster-backup/OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - mumoshu 3 | - jollinshead 4 | reviewers: 5 | - mumoshu 6 | - jollinshead -------------------------------------------------------------------------------- /contrib/cluster-backup/README.md: -------------------------------------------------------------------------------- 1 | # Restore 2 | Restoring a Kubernetes environment (from a previous backup) may be executed through the use of the bash script: [kubernetes-on-aws-restore.sh](/contrib/cluster-backup/restore.sh) 3 | 4 | The script was designed to be used by the cluster provisioner and assumes that their local machine has: 5 | - 'kubectl' installed locally 6 | - 'aws' installed locally 7 | - 'jq' installed locally 8 | - 'kubeconfig' configured correctly 9 | - 'jq' configured correctly 10 | - cluster is reachable 11 | 12 | The script will **(kubectl) create** Kubernetes resources per namespace. All namespaces (and associated resources) are restored by default with the exception of namespaces: 'kube-system' and 'default'. 13 | To whitelist namespaces, include the desired namespace names as additional arguments after the initial BUCKET_URI argument. 14 | The BUCKET_URI argument is the URL of the timestamped folder. 15 | 16 | ## Implementation 17 | 1) The script will initially validate that the namespaces it is to restore are not already existing. 18 | 2) Kubernetes resources common to all namespaces (such as *StorageClass*) are created. 19 | 3) For each namespace to restore, the namespace is created and Kubernetes resources within the namespace are created. 20 | 21 | ### Considerations 22 | - Restoring *PersistentVolumes* does not provision new volumes from AWS - it assumes such volumes already exist in AWS. 23 | - You may have to tailor the script for your specific needs. 24 | 25 | 26 | ### Example 27 | 28 | Following the deletion of a cluster, another newly created cluster is to be restored with the same Kubernetes environment. 29 | 30 | A backup was previously exported to S3 to the path: 31 | ``` 32 | s3://my-bucket-name/my-cluster-name/backup/17-05-04_13-48-33 33 | ``` 34 | After the new cluster has been provisioned and all nodes are up and operational, the provisioner restores the cluster: 35 | ``` 36 | $ kubernetes-on-aws-restore.sh s3://my-bucket-name/my-cluster-name/backup/17-05-04_13-48-33 37 | ``` 38 | -------------------------------------------------------------------------------- /contrib/dex/dex.cm.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: dex 5 | namespace: kube-system 6 | data: 7 | config.yaml: | 8 | issuer: https://dex.example.com # replace with your url 9 | storage: 10 | type: kubernetes 11 | config: 12 | inCluster: true 13 | web: 14 | http: 0.0.0.0:5556 15 | frontend: 16 | theme: 'coreos' 17 | issuer: 'KubeAWS Identity' 18 | connectors: 19 | - type: github 20 | id: github 21 | name: GitHub 22 | config: 23 | clientID: $GITHUB_CLIENT_ID 24 | clientSecret: $GITHUB_CLIENT_SECRET 25 | redirectURI: https://dex.example.com/callback # replace with your url 26 | org: kubernetes 27 | oauth2: 28 | skipApprovalScreen: true 29 | staticClients: 30 | - id: example-app 31 | redirectURIs: 32 | - 'http://127.0.0.1:5555' # replace with your Example App url 33 | name: 'example-app' 34 | secret: 'ZXhhbXBsZS1hcHAtc2VjcmV0' 35 | enablePasswordDB: true 36 | staticPasswords: 37 | - email: "admin@example.com" 38 | # bcrypt hash of the string "password" 39 | hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" 40 | username: "admin" 41 | userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" -------------------------------------------------------------------------------- /contrib/dex/dex.de.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dex 5 | namespace: kube-system 6 | labels: 7 | app: dex 8 | component: identity 9 | annotations: 10 | scheduler.alpha.kubernetes.io/critical-pod: "" 11 | spec: 12 | replicas: 1 13 | minReadySeconds: 30 14 | strategy: 15 | rollingUpdate: 16 | maxUnavailable: 0 17 | selector: 18 | matchLabels: 19 | app: dex 20 | component: identity 21 | template: 22 | metadata: 23 | name: dex 24 | labels: 25 | app: dex 26 | component: identity 27 | spec: 28 | volumes: 29 | - name: config 30 | configMap: 31 | name: dex 32 | items: 33 | - key: config.yaml 34 | path: config.yaml 35 | containers: 36 | - name: dex 37 | imagePullPolicy: IfNotPresent 38 | image: quay.io/coreos/dex:v2.5.0 39 | command: ["/usr/local/bin/dex", "serve", "/etc/dex/config.yaml"] 40 | volumeMounts: 41 | - name: config 42 | mountPath: /etc/dex 43 | ports: 44 | - containerPort: 5556 45 | protocol: TCP 46 | livenessProbe: 47 | httpGet: 48 | path: /healthz 49 | port: 5556 50 | initialDelaySeconds: 5 51 | timeoutSeconds: 1 52 | env: 53 | - name: GITHUB_CLIENT_ID 54 | valueFrom: 55 | secretKeyRef: 56 | name: dex 57 | key: client-id 58 | - name: GITHUB_CLIENT_SECRET 59 | valueFrom: 60 | secretKeyRef: 61 | name: dex 62 | key: client-secret 63 | resources: 64 | requests: 65 | cpu: 100m 66 | memory: 50Mi 67 | limits: 68 | cpu: 100m 69 | memory: 50Mi 70 | -------------------------------------------------------------------------------- /contrib/dex/elb/dex.external-elb.svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:xx-xxxx-x:xxxxxxxxx:xxxxxxx/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxx #replace this value 6 | service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http 7 | domainName: "dex.example.com" #replace this value 8 | name: dex 9 | namespace: kube-system 10 | labels: 11 | app: dex 12 | component: identity 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 443 17 | targetPort: 5556 18 | protocol: TCP 19 | selector: 20 | app: dex -------------------------------------------------------------------------------- /contrib/dex/elb/dex.internal-elb.svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: dex 5 | namespace: kube-system 6 | labels: 7 | run: dex 8 | annotations: 9 | service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:xx-xxxx-x:xxxxxxxxx:xxxxxxx/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxx #replace this value 10 | service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http 11 | service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0 12 | domainName: "dex.example.com" #replace this value 13 | spec: 14 | type: LoadBalancer 15 | ports: 16 | - port: 443 17 | targetPort: 5556 18 | protocol: TCP 19 | selector: 20 | app: dex -------------------------------------------------------------------------------- /contrib/dex/ingress/dex.nginx.ing.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: dex-ingress 5 | namespace: kube-system 6 | annotations: 7 | ingress.kubernetes.io/ssl-redirect: "true" 8 | ingress.kubernetes.io/use-port-in-redirects: "true" 9 | kubernetes.io/tls-acme: "true" 10 | spec: 11 | tls: 12 | - secretName: dex-tls-secret 13 | hosts: 14 | - dex.example.com 15 | rules: 16 | - host: dex.example.com 17 | http: 18 | paths: 19 | - path: / 20 | backend: 21 | serviceName: dex 22 | servicePort: 5556 23 | -------------------------------------------------------------------------------- /contrib/dex/ingress/dex.svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: dex 5 | namespace: kube-system 6 | labels: 7 | app: dex 8 | component: identity 9 | spec: 10 | selector: 11 | app: dex 12 | component: identity 13 | ports: 14 | - name: http 15 | protocol: TCP 16 | port: 5556 17 | -------------------------------------------------------------------------------- /core/root/credentials.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubernetes-incubator/kube-aws/core/root/defaults" 6 | "github.com/kubernetes-incubator/kube-aws/credential" 7 | "github.com/kubernetes-incubator/kube-aws/logger" 8 | "github.com/kubernetes-incubator/kube-aws/pki" 9 | "io/ioutil" 10 | "os" 11 | "path" 12 | "strings" 13 | ) 14 | 15 | func RenderCredentials(configPath string, renderCredentialsOpts credential.GeneratorOptions) error { 16 | opts := NewOptions(false, false) 17 | cluster, err := CompileClusterFromFile(configPath, opts, renderCredentialsOpts.AwsDebug) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | if err := os.MkdirAll(defaults.AssetsDir, 0700); err != nil { 23 | return err 24 | } 25 | 26 | if _, err = cluster.GenerateAssetsOnDisk(defaults.AssetsDir, renderCredentialsOpts); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func LoadCertificates() (map[string]pki.Certificates, error) { 34 | 35 | if _, err := os.Stat(defaults.AssetsDir); os.IsNotExist(err) { 36 | return nil, fmt.Errorf("%s does not exist, run 'render credentials' first", defaults.AssetsDir) 37 | } 38 | 39 | files, err := ioutil.ReadDir(defaults.AssetsDir) 40 | if err != nil { 41 | return nil, fmt.Errorf("cannot read files from %s: %v", defaults.AssetsDir, err) 42 | } 43 | 44 | certs := make(map[string]pki.Certificates) 45 | for _, f := range files { 46 | if f.IsDir() || !strings.HasSuffix(f.Name(), ".pem") { 47 | continue 48 | } 49 | b, err := ioutil.ReadFile(path.Join(defaults.AssetsDir, f.Name())) 50 | if err != nil { 51 | logger.Warnf("cannot read %q file: %v", f.Name(), err) 52 | continue 53 | } 54 | if !pki.IsCertificatePEM(b) { 55 | continue 56 | } 57 | c, err := pki.CertificatesFromBytes(b) 58 | if err != nil { 59 | logger.Warnf("cannot parse %q file: %v", f.Name(), err) 60 | continue 61 | } 62 | certs[f.Name()] = c 63 | } 64 | return certs, nil 65 | } 66 | -------------------------------------------------------------------------------- /core/root/defaults/defaults.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | const ( 4 | AssetsDir = "credentials" 5 | ControllerTmplFile = "userdata/cloud-config-controller" 6 | WorkerTmplFile = "userdata/cloud-config-worker" 7 | EtcdTmplFile = "userdata/cloud-config-etcd" 8 | ControlPlaneStackTemplateTmplFile = "stack-templates/control-plane.json.tmpl" 9 | NetworkStackTemplateTmplFile = "stack-templates/network.json.tmpl" 10 | EtcdStackTemplateTmplFile = "stack-templates/etcd.json.tmpl" 11 | NodePoolStackTemplateTmplFile = "stack-templates/node-pool.json.tmpl" 12 | RootStackTemplateTmplFile = "stack-templates/root.json.tmpl" 13 | ) 14 | -------------------------------------------------------------------------------- /core/root/destroyer.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kubernetes-incubator/kube-aws/awsconn" 7 | "github.com/kubernetes-incubator/kube-aws/cfnstack" 8 | "github.com/kubernetes-incubator/kube-aws/core/root/config" 9 | ) 10 | 11 | type DestroyOptions struct { 12 | Profile string 13 | AwsDebug bool 14 | Force bool 15 | } 16 | 17 | type ClusterDestroyer interface { 18 | Destroy() error 19 | } 20 | 21 | type clusterDestroyerImpl struct { 22 | underlying *cfnstack.Destroyer 23 | } 24 | 25 | func ClusterDestroyerFromFile(configPath string, opts DestroyOptions) (ClusterDestroyer, error) { 26 | cfg, err := config.ConfigFromFile(configPath) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | session, err := awsconn.NewSessionFromRegion(cfg.Region, opts.AwsDebug, opts.Profile) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed to establish aws session: %v", err) 34 | } 35 | 36 | cfnDestroyer := cfnstack.NewDestroyer(cfg.RootStackName(), session, cfg.CloudFormation.RoleARN) 37 | return clusterDestroyerImpl{ 38 | underlying: cfnDestroyer, 39 | }, nil 40 | } 41 | 42 | func (d clusterDestroyerImpl) Destroy() error { 43 | return d.underlying.Destroy() 44 | } 45 | -------------------------------------------------------------------------------- /core/root/generate.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "github.com/gobuffalo/packr" 9 | "github.com/kubernetes-incubator/kube-aws/builtin" 10 | "github.com/kubernetes-incubator/kube-aws/filegen" 11 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 12 | "github.com/kubernetes-incubator/kube-aws/pkg/model" 13 | "os" 14 | "strings" 15 | ) 16 | 17 | func RenderStack(configPath string) error { 18 | 19 | c, err := model.ClusterFromFile(configPath) 20 | if err != nil { 21 | return err 22 | } 23 | config, err := model.Compile(c, api.ClusterOptions{}) 24 | kubeconfig, err := generateKubeconfig(config) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | ignoredWords := []string{ 30 | "etcdadm", 31 | "kubeconfig.tmpl", 32 | "cluster.yaml.tmpl", 33 | } 34 | 35 | if err := builtin.Box().Walk(func(path string, file packr.File) error { 36 | for _, f := range ignoredWords { 37 | if strings.Contains(path, f) { 38 | fmt.Fprintf(os.Stderr, "ignored %s\n", path) 39 | return nil 40 | } 41 | } 42 | content, err := builtin.Box().MustBytes(path) 43 | if err != nil { 44 | return err 45 | } 46 | gen := filegen.File(path, content, 0644) 47 | return filegen.Render(gen) 48 | }); err != nil { 49 | return err 50 | } 51 | 52 | if err := filegen.Render( 53 | filegen.File("kubeconfig", kubeconfig, 0600), 54 | ); err != nil { 55 | return err 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func generateKubeconfig(clusterConfig *model.Config) ([]byte, error) { 62 | 63 | tmpl, err := template.New("kubeconfig.yaml").Parse(builtin.String("kubeconfig.tmpl")) 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to parse default kubeconfig template: %v", err) 66 | } 67 | 68 | var kubeconfig bytes.Buffer 69 | if err := tmpl.Execute(&kubeconfig, clusterConfig); err != nil { 70 | return nil, fmt.Errorf("failed to render kubeconfig: %v", err) 71 | } 72 | return kubeconfig.Bytes(), nil 73 | } 74 | -------------------------------------------------------------------------------- /core/root/operation_targets.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import "strings" 4 | 5 | const ( 6 | OperationTargetAll = "all" 7 | ) 8 | 9 | type OperationTargets []string 10 | 11 | func AllOperationTargetsAsStringSlice() []string { 12 | return []string{"all"} 13 | } 14 | 15 | func AllOperationTargetsWith(nodePoolNames []string, operationTargetNames []string) OperationTargets { 16 | ts := []string{} 17 | ts = append(ts, operationTargetNames...) 18 | ts = append(ts, nodePoolNames...) 19 | return OperationTargets(ts) 20 | } 21 | 22 | func OperationTargetsFromStringSlice(targets []string) OperationTargets { 23 | return OperationTargets(targets) 24 | } 25 | 26 | func (ts OperationTargets) IncludeWorker(nodePoolName string) bool { 27 | for _, t := range ts { 28 | if t == nodePoolName { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | func (ts OperationTargets) IncludeNetwork(networkStackName string) bool { 36 | for _, t := range ts { 37 | if t == networkStackName { 38 | return true 39 | } 40 | } 41 | return false 42 | } 43 | 44 | func (ts OperationTargets) IncludeControlPlane(controlPlaneStackName string) bool { 45 | for _, t := range ts { 46 | if t == controlPlaneStackName { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | 53 | func (ts OperationTargets) IncludeEtcd(etcdStackName string) bool { 54 | for _, t := range ts { 55 | if t == etcdStackName { 56 | return true 57 | } 58 | } 59 | return false 60 | } 61 | func (ts OperationTargets) IncludeAll(cl *Cluster) bool { 62 | return ts.IncludeNetwork(cl.Network().Config.NetworkStackName()) && 63 | ts.IncludeControlPlane(cl.ControlPlane().Config.ControlPlaneStackName()) && 64 | ts.IncludeEtcd(cl.Etcd().Config.EtcdStackName()) 65 | } 66 | 67 | func (ts OperationTargets) IsAll() bool { 68 | for _, t := range ts { 69 | if t == OperationTargetAll { 70 | return true 71 | } 72 | } 73 | return false 74 | } 75 | 76 | func (ts OperationTargets) String() string { 77 | return strings.Join(ts, ", ") 78 | } 79 | -------------------------------------------------------------------------------- /core/root/options.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import "github.com/kubernetes-incubator/kube-aws/core/root/defaults" 4 | 5 | type options struct { 6 | AssetsDir string 7 | ControllerTmplFile string 8 | WorkerTmplFile string 9 | EtcdTmplFile string 10 | RootStackTemplateTmplFile string 11 | ControlPlaneStackTemplateTmplFile string 12 | NetworkStackTemplateTmplFile string 13 | EtcdStackTemplateTmplFile string 14 | NodePoolStackTemplateTmplFile string 15 | AWSProfile string 16 | SkipWait bool 17 | PrettyPrint bool 18 | } 19 | 20 | func NewOptions(prettyPrint bool, skipWait bool, awsProfile ...string) options { 21 | var profile string 22 | if len(awsProfile) > 0 { 23 | profile = awsProfile[0] 24 | } 25 | 26 | return options{ 27 | AssetsDir: defaults.AssetsDir, 28 | ControllerTmplFile: defaults.ControllerTmplFile, 29 | WorkerTmplFile: defaults.WorkerTmplFile, 30 | EtcdTmplFile: defaults.EtcdTmplFile, 31 | ControlPlaneStackTemplateTmplFile: defaults.ControlPlaneStackTemplateTmplFile, 32 | NetworkStackTemplateTmplFile: defaults.NetworkStackTemplateTmplFile, 33 | EtcdStackTemplateTmplFile: defaults.EtcdStackTemplateTmplFile, 34 | NodePoolStackTemplateTmplFile: defaults.NodePoolStackTemplateTmplFile, 35 | RootStackTemplateTmplFile: defaults.RootStackTemplateTmplFile, 36 | AWSProfile: profile, 37 | SkipWait: skipWait, 38 | PrettyPrint: prettyPrint, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /credential/encrypted_file.go: -------------------------------------------------------------------------------- 1 | package credential 2 | 3 | import "io/ioutil" 4 | 5 | func (c *EncryptedFile) Fingerprint() string { 6 | return c.fingerprint 7 | } 8 | 9 | func (c *EncryptedFile) Persist() error { 10 | if err := ioutil.WriteFile(c.filePath, c.content, 0600); err != nil { 11 | return err 12 | } 13 | if c.fingerprint != "" { 14 | return ioutil.WriteFile(c.fingerprintFilePath, []byte(c.fingerprint), 0600) 15 | } 16 | return nil 17 | } 18 | 19 | func (c *EncryptedFile) String() string { 20 | return string(c.content) 21 | } 22 | 23 | func (c *EncryptedFile) Bytes() []byte { 24 | return c.content 25 | } 26 | 27 | func (c *EncryptedFile) SetBytes(bytes []byte) { 28 | c.content = bytes 29 | } 30 | -------------------------------------------------------------------------------- /credential/kms_encryptor.go: -------------------------------------------------------------------------------- 1 | package credential 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/service/kms" 6 | ) 7 | 8 | func (s KMSEncryptor) EncryptedBytes(data []byte) ([]byte, error) { 9 | if len(data) == 0 { 10 | return []byte{}, nil 11 | } 12 | 13 | encryptInput := kms.EncryptInput{ 14 | KeyId: aws.String(s.KmsKeyARN), 15 | Plaintext: data, 16 | } 17 | encryptOutput, err := s.KmsSvc.Encrypt(&encryptInput) 18 | if err != nil { 19 | return []byte{}, err 20 | } 21 | return encryptOutput.CiphertextBlob, nil 22 | } 23 | -------------------------------------------------------------------------------- /credential/pki.go: -------------------------------------------------------------------------------- 1 | package credential 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/kubernetes-incubator/kube-aws/logger" 9 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 10 | "github.com/kubernetes-incubator/kube-aws/pki" 11 | ) 12 | 13 | type ProtectedPKI struct { 14 | Encryptor 15 | *pki.PKI 16 | } 17 | 18 | func NewProtectedPKI(enc Encryptor) *ProtectedPKI { 19 | return &ProtectedPKI{ 20 | Encryptor: enc, 21 | PKI: pki.NewPKI(), 22 | } 23 | } 24 | 25 | func (ppki *ProtectedPKI) CreateKeyaPair(spec api.KeyPairSpec) error { 26 | var signer *pki.KeyPair 27 | if spec.Signer != "" { 28 | signerCert, err := ioutil.ReadFile(spec.SignerCertPath()) 29 | if err != nil { 30 | return fmt.Errorf("failed to read signer certificate %s for creating %s: %v", spec.SignerCertPath(), spec.Name, err) 31 | } 32 | signerKey, err := ioutil.ReadFile(spec.SignerKeyPath()) 33 | if err != nil { 34 | return fmt.Errorf("failed to read signer key %s for creating %s: %v", spec.SignerKeyPath(), spec.Name, err) 35 | } 36 | signer, err = pki.KeyPairFromPEMs(spec.Signer, signerCert, signerKey) 37 | } 38 | keypair, err := ppki.GenerateKeyPair(spec, signer) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | keypath := spec.KeyPath() 44 | keypem := keypair.KeyInPEM() 45 | logger.Infof("Writing key pem file %s", keypath) 46 | if err := ioutil.WriteFile(keypath, keypem, 0644); err != nil { 47 | return err 48 | } 49 | 50 | crtpath := spec.CertPath() 51 | crtpem := keypair.CertInPEM() 52 | logger.Infof("Writing certificate pem file %s", crtpath) 53 | if err := ioutil.WriteFile(crtpath, crtpem, 0644); err != nil { 54 | return err 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func (ppki *ProtectedPKI) EnsureKeyPairsCreated(specs []api.KeyPairSpec) error { 61 | for _, spec := range specs { 62 | keypath := spec.KeyPath() 63 | shapath := spec.KeyPath() + ".fingerprint" 64 | encpath := spec.EncryptedKeyPath() 65 | crtpath := spec.CertPath() 66 | if !fileExists(keypath) && !fileExists(encpath) && !fileExists(shapath) && !fileExists(crtpath) { 67 | if err := ppki.CreateKeyaPair(spec); err != nil { 68 | return err 69 | } 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | func fileExists(path string) bool { 76 | info, err := os.Stat(path) 77 | if os.IsNotExist(err) { 78 | return false 79 | } 80 | return !info.IsDir() 81 | } 82 | -------------------------------------------------------------------------------- /credential/plaintext_file.go: -------------------------------------------------------------------------------- 1 | package credential 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | ) 7 | 8 | func (c *PlaintextFile) Fingerprint() string { 9 | return calculateFingerprint(c.content) 10 | } 11 | 12 | func (c *PlaintextFile) Persist() error { 13 | if len(c.content) == 0 { 14 | return fmt.Errorf("%s is going to be empty. Maybe a bug", c.filePath) 15 | } 16 | if err := ioutil.WriteFile(c.filePath, c.content, 0600); err != nil { 17 | return err 18 | } 19 | return nil 20 | } 21 | 22 | func (c *PlaintextFile) String() string { 23 | return string(c.content) 24 | } 25 | 26 | func (c *PlaintextFile) Bytes() []byte { 27 | return c.content 28 | } 29 | -------------------------------------------------------------------------------- /credential/store.go: -------------------------------------------------------------------------------- 1 | package credential 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/kubernetes-incubator/kube-aws/logger" 8 | ) 9 | 10 | func (e Store) EncryptedCredentialFromPath(filePath string, defaultValue *string) (*EncryptedFile, error) { 11 | raw, errRaw := RawCredentialFileFromPath(filePath, defaultValue) 12 | cache, err := EncryptedCredentialCacheFromPath(filePath, errRaw == nil) 13 | if err != nil { 14 | if errRaw != nil { // if neither .enc nor raw is there, it is an error 15 | return nil, fmt.Errorf("Error reading raw file: %v", errRaw) 16 | } 17 | cache, err = EncryptedCredentialCacheFromRawCredential(raw, e.Encryptor) 18 | if err != nil { 19 | return nil, err 20 | } 21 | logger.Debugf("generated \"%s\" by encrypting \"%s\"\n", cache.filePath, raw.filePath) 22 | } else { 23 | // we verify fingreprints only if non .enc version is present, so there is something there to compare against 24 | // otherwise we assume that user provided correct .enc files to be used as-is 25 | if errRaw == nil && raw.Fingerprint() != cache.Fingerprint() { 26 | logger.Debugf("\"%s\" is not up-to-date. kube-aws is regenerating it from \"%s\"\n", cache.filePath, raw.filePath) 27 | cache, err = EncryptedCredentialCacheFromRawCredential(raw, e.Encryptor) 28 | if err != nil { 29 | return nil, err 30 | } 31 | } else if errRaw != nil && !os.IsNotExist(errRaw) { 32 | return nil, fmt.Errorf("Error reading existing raw file: %v", errRaw) 33 | } 34 | } 35 | 36 | return cache, nil 37 | } 38 | -------------------------------------------------------------------------------- /credential/types.go: -------------------------------------------------------------------------------- 1 | package credential 2 | 3 | import "github.com/aws/aws-sdk-go/service/kms" 4 | 5 | type PlaintextFile struct { 6 | content []byte 7 | filePath string 8 | } 9 | 10 | // The fact KMS encryption produces different ciphertexts for the same plaintext had been 11 | // causing unnecessary node replacements(https://github.com/kubernetes-incubator/kube-aws/issues/107) 12 | // Persist encrypted assets for caching purpose so that we can avoid that. 13 | type EncryptedFile struct { 14 | content []byte 15 | filePath string 16 | fingerprintFilePath string 17 | fingerprint string 18 | } 19 | 20 | type Store struct { 21 | Encryptor Encryptor 22 | } 23 | 24 | type KMSEncryptionService interface { 25 | Encrypt(*kms.EncryptInput) (*kms.EncryptOutput, error) 26 | } 27 | 28 | type Encryptor interface { 29 | EncryptedBytes(raw []byte) ([]byte, error) 30 | } 31 | 32 | type KMSEncryptor struct { 33 | KmsKeyARN string 34 | KmsSvc KMSEncryptionService 35 | } 36 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Introduction {#introduction} 2 | 3 | kube-aws is a command-line tool to create/update/destroy Kubernetes clusters on AWS. The [full manual can be found here](https://kubernetes-incubator.github.io/kube-aws/). 4 | 5 | To start using kube-aws, try the [Getting Started Guide](getting-started/README.md). 6 | 7 | Once you are familiar with the basic setup, the sections [Add-Ons](add-ons/README.md) and some [Advanced Topics](advanced-topics/README.md) cover additional setup, use cases and configuration. 8 | 9 | [![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes-incubator/kube-aws)](https://goreportcard.com/report/github.com/kubernetes-incubator/kube-aws) [![Build Status](https://travis-ci.org/kubernetes-incubator/kube-aws.svg?branch=master)](https://travis-ci.org/kubernetes-incubator/kube-aws) [![License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](LICENSE) 10 | 11 | 12 | # Features {#features} 13 | 14 | * Create, update and destroy Kubernetes clusters on AWS 15 | * Highly available and scalable Kubernetes clusters backed by multi-AZ deployment and Node Pools 16 | * Deployment to an existing VPC 17 | * Powered by various AWS services including CloudFormation, KMS, Auto Scaling, Spot Fleet, EC2, ELB, S3, etc 18 | 19 | # Kubernetes Incubator 20 | 21 | kube-aws is a [Kubernetes Incubator project](https://github.com/kubernetes/community/blob/master/incubator.md). The project was established 2017-03-15. The incubator team for the project is: 22 | 23 | - Sponsor: Tim Hockin (@thockin) 24 | - Champion: Mike Danese (@mikedanese) 25 | - SIG: sig-aws 26 | 27 | # Announcements 28 | 29 | Older releases of kube-aws had been signed by the CoreOS key and were verifiable with the [CoreOS Application Signing Public Key](https://coreos.com/security/app-signing-key/). This was when kube-aws was maintained primarily by CoreOS. However, the signing process has been postponed since v0.9.3 since the comm. Please read the [issue \#288](https://github.com/kubernetes-incubator/kube-aws/issues/288) for more information. 30 | 31 | # Documentation Updates 32 | 33 | Please [contact us](getting-in-touch.md) if you wish to see a topic added to the manual. 34 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Home](README.md) 4 | * [Quick Start](tutorials/quick-start.md) 5 | * [Getting Started](getting-started/README.md) 6 | * [Prerequisites](getting-started/prerequisites.md) 7 | * [Step 1: Configure](getting-started/step-1-configure.md) 8 | * [Step 2: Render](getting-started/step-2-render.md) 9 | * [Step 3: Launch](getting-started/step-3-launch.md) 10 | * [Step 4: Update](getting-started/step-4-update.md) 11 | * [Step 5: Add Node Pool](getting-started/step-5-add-node-pool.md) 12 | * [Step 6: Configure Add-ons](getting-started/step-6-configure-add-ons.md) 13 | * [Step 7: Destroy](getting-started/step-7-destroy.md) 14 | * [Getting In Touch](getting-in-touch.md) 15 | * [CLI Reference](cli-reference/README.md) 16 | * [AWS Credentials](cli-reference/aws-credentials.md) 17 | * [Add-ons](add-ons/README.md) 18 | * [Cluster Resource Backup to AWS S3](add-ons/cluster-resource-backup-to-s3.md) 19 | * [Journald Logging to AWS CloudWatch](add-ons/journald-logging-to-cloudwatch.md) 20 | * [Guides](guides/README.md) 21 | * [Developer Guide](guides/developer-guide.md) 22 | * [Operator Guide](guides/operator-guide.md) 23 | * [Advanced Topics](advanced-topics/README.md) 24 | * [CloudFormation Updates in CLI](advanced-topics/cloudformation-updates-in-cli.md) 25 | * [etcd Backup & Restore](advanced-topics/etcd-backup-and-restore.md) 26 | * [Kubernetes Dashboard Access](advanced-topics/kubernetes-dashboard.md) 27 | * [Use An Existing VPC](advanced-topics/use-an-existing-vpc.md) 28 | * [Troubleshooting](troubleshooting/README.md) 29 | * [Known Limitations](troubleshooting/known-limitations.md) 30 | * [Common Problems](troubleshooting/common-problems.md) 31 | -------------------------------------------------------------------------------- /docs/add-ons/README.md: -------------------------------------------------------------------------------- 1 | # Add-ons 2 | 3 | These are optional features which can be enabled using kube-aws. 4 | 5 | * [Cluster Resource Backup to AWS S3](cluster-resource-backup-to-s3.md) - automated backup and restore your Kubernetes resources to S3 6 | * [Journald Logging to AWS CloudWatch](journald-logging-to-cloudwatch.md) - stream journald logs into CloudWatch and also to some CLI commands such as `kube-aws apply` -------------------------------------------------------------------------------- /docs/advanced-topics/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Topics 2 | 3 | * [CloudFormation Streaming](cloudformation-updates-in-cli.md) - stream CloudFormation updates during CLI commands `kube-aws apply` 4 | * [etcd Backup & Restore](etcd-backup-and-restore.md) - how to backup and restore etcd either manually or automatically 5 | * [Kubernetes Dashboard Access](kubernetes-dashboard.md) - how to expose and access the Kubernetes Dashboard 6 | * [Use An Existing VPC](use-an-existing-vpc.md) - how to deploy a Kubernetes cluster to an existing VPC 7 | -------------------------------------------------------------------------------- /docs/advanced-topics/cloudformation-updates-in-cli.md: -------------------------------------------------------------------------------- 1 | # CloudFormation Streaming 2 | 3 | CloudFormation stack events can be streamed to the console (stdout) during a `kube-aws apply` operation. 4 | 5 | The format of the messages are: 6 | ``` 7 | TimePassed StackName ResourceStatus LogicalResourceId StatusReason 8 | ``` 9 | For example: 10 | ``` 11 | +00:02:45 Nodepoolb CREATE_IN_PROGRESS WorkersLC "Resource creation Initiated" 12 | +00:02:45 Nodepoolb CREATE_COMPLETE WorkersLC 13 | +00:02:48 Nodepoolb CREATE_IN_PROGRESS Workers 14 | ``` 15 | 16 | This feature is enabled by default and configurable in cluster.yaml: 17 | 18 | ``` 19 | cloudFormationStreaming: true 20 | ``` -------------------------------------------------------------------------------- /docs/advanced-topics/high-availability.md: -------------------------------------------------------------------------------- 1 | # High Availability 2 | 3 | To achieve high availability using kube-aws, it is recommended to: 4 | 5 | * Specify at least 3 for `etcd.count` in `cluster.yaml`. See [Optimal Cluster Size](https://coreos.com/etcd/docs/latest/v2/admin_guide.html#optimal-cluster-size) for details of etcd recommendations 6 | * Specify at least 2 for `controller.count` in `cluster.yaml` 7 | * Use 2 or more worker nodes, 8 | * Avoid `t2.medium` or smaller instances for etcd and controller nodes. See [this issue](https://github.com/kubernetes-incubator/kube-aws/issues/138) for some additional discussion. 9 | 10 | # Additional Reading 11 | 12 | There's some additional documentation about [Building High-Availability Clusters](https://kubernetes.io/docs/admin/high-availability/) on the main Kubernetes documentation site. Although kube-aws will taken care of most of those concerns for you, it can be worth a read for a deeper understanding. 13 | -------------------------------------------------------------------------------- /docs/advanced-topics/network-topologies.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-retired/kube-aws/953e56eaaf1a04144f76c93dc8ceb0a3579c7953/docs/advanced-topics/network-topologies.md -------------------------------------------------------------------------------- /docs/advanced-topics/use-an-existing-vpc.md: -------------------------------------------------------------------------------- 1 | # Deploying to an existing VPC 2 | 3 | kube-aws tries its best to not modify your existing AWS resources. It's our users' responsibility to ensure existing AWS resources provided to kube-aws are properly configured. 4 | 5 | Please note that you don't need to care about modifications if you've instructed kube-aws to create all the AWS resources for you i.e. you've omitted `vpcId` and `routeTableId` from `cluster.yaml`. 6 | 7 | ## What kube-aws does modify 8 | 9 | * Adding a record set for Kubernetes API Endpoint to an existing hosted zone you've provided via the `hostedZoneId` configuration key in `cluster.yaml` 10 | * Adding one or more subnet\(s\) to an existing VPC specified by the `vpcId` 11 | * Associating one or more subnet\(s\) to an existing route table specified by the `routeTableId` 12 | 13 | See [`cluster.yaml.tmpl`](https://github.com/kubernetes-incubator/kube-aws/blob/master/builtin/files/cluster.yaml.tmpl) for more details. 14 | 15 | All the other configurations for existing AWS resources must be done properly by users before kube-aws is run. 16 | 17 | For example, if you're deploying a cluster to an existing VPC: 18 | 19 | * An internet gateway or a NAT gateway needs to be added to VPC before cluster can be created 20 | * Or [all the nodes will fail to launch because they can't pull docker images or ACIs required to run essential processes like fleet, hyperkube, etcd, awscli, cfn-signal, cfn-init](https://github.com/kubernetes-incubator/kube-aws/issues/120). 21 | * Existing route tables must have a route to Internet in some form. For example, a default route to an internet gateway or to a NAT gateway via `0.0.0.0/0` would be needed or your cluster won't come up. See [a relevant issue about it](https://github.com/kubernetes-incubator/kube-aws/issues/121#issuecomment-266255407). 22 | * Existing route tables and/or subnets to be reused by kube-aws must be tagged with the key `kubernetes.io/cluster/$CLUSTER_NAME` and "shared" as a value. 23 | * Or [Kubernetes will fail to create ELBs correspond to Kubernetes services with `type=LoadBalancer`](https://github.com/kubernetes-incubator/kube-aws/issues/135) 24 | * ["DNS Hostnames" must be turned on before cluster can be created](https://github.com/kubernetes-incubator/kube-aws/issues/119) 25 | * Or etcd nodes are unable to communicate each other thus the cluster doesn't work at all -------------------------------------------------------------------------------- /docs/cli-reference/aws-credentials.md: -------------------------------------------------------------------------------- 1 | # AWS Credentials 2 | 3 | Configure your local workstation with AWS credentials using one of the following methods. 4 | 5 | ## Method 1: `configure` command 6 | 7 | Provide the values of your AWS access and secret keys, and optionally default region and output format: 8 | 9 | ```bash 10 | $ aws configure 11 | AWS Access Key ID [None]: AKID1234567890 12 | AWS Secret Access Key [None]: MY-SECRET-KEY 13 | Default region name [None]: us-west-1 14 | Default output format [None]: text 15 | ``` 16 | 17 | ## Method 2: Environment Variables 18 | 19 | Provide AWS credentials to kube-aws by exporting the following environment variables: 20 | 21 | ```bash 22 | export AWS_ACCESS_KEY_ID=AKID1234567890 23 | export AWS_SECRET_ACCESS_KEY=MY-SECRET-KEY 24 | ``` 25 | 26 | Alternatively, you can provide a AWS profile to kube-aws via the `AWS_PROFILE` environment variable such as: 27 | 28 | ``` 29 | AWS_PROFILE=my-profile-name kube-aws init ... 30 | ``` 31 | 32 | ### Multi-Factor Authentication (MFA) 33 | 34 | If you are using MFA, you need to export the Session Token as well: 35 | 36 | ```bash 37 | export AWS_SESSION_TOKEN=MY-SESSION-TOKEN 38 | ``` -------------------------------------------------------------------------------- /docs/getting-in-touch.md: -------------------------------------------------------------------------------- 1 | # Issues 2 | 3 | Issues can be [reported in GitHub](https://github.com/kubernetes-incubator/kube-aws/issues), it is advised to search for existing issues prior to reporting. 4 | 5 | # Community Chat 6 | 7 | Please feel free to reach out to the kube-aws community on the Kubernetes [\#kube-aws Slack channel](https://kubernetes.slack.com/messages/C5GP8LPEC/). 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Deploy a fully-functional Kubernetes cluster using AWS CloudFormation. Your cluster will be configured to use AWS features to enhance Kubernetes. For example, Kubernetes may automatically provision an Elastic Load Balancer for each Kubernetes Service. 4 | 5 | The [kube-aws](https://github.com/kubernetes-incubator/kube-aws/releases) CLI tool can be used to automate cluster deployment to AWS. 6 | 7 | After completing this guide, a deployer will be able to interact with the Kubernetes API from their workstation using the `kubectl` CLI tool. 8 | 9 | Each of the steps will cover: 10 | 11 | * [Pre-requisites][getting-started-prerequisites.md] 12 | * [Step 1: Configure][getting-started-step-1] 13 | * Download the latest release of kube-aws 14 | * Define account and cluster settings 15 | * [Step 2: Render][getting-started-step-2] 16 | * Compile a re-usable CloudFormation template for the cluster 17 | * Optionally adjust template configuration 18 | * Validate the rendered CloudFormation stack 19 | * [Step 3: Launch][getting-started-step-3] 20 | * Create the CloudFormation stack and start our EC2 machines 21 | * Set up CLI access to the new cluster 22 | * [Step 4: Update][getting-started-step-4] 23 | * Update the CloudFormation stack 24 | * [Step 5: Add Node Pool][getting-started-step-5] 25 | * Create the additional pool of worker nodes 26 | * Adjust template configuration for each pool of worker nodes 27 | * [Step 6: Configure add-ons][getting-started-step-6] 28 | * Configure various Kubernetes add-ons 29 | * [Step 7: Destroy][getting-started-step-7] 30 | * Destroy the cluster 31 | 32 | Let's get started with [Step 1](step-1-configure.md)! 33 | 34 | [getting-started-prerequisites.md]: prerequisites.md 35 | [getting-started-step-1]: step-1-configure.md 36 | [getting-started-step-2]: step-2-render.md 37 | [getting-started-step-3]: step-3-launch.md 38 | [getting-started-step-4]: step-4-update.md 39 | [getting-started-step-5]: step-5-add-node-pool.md 40 | [getting-started-step-6]: step-6-configure-add-ons.md 41 | [getting-started-step-7]: step-7-destroy.md 42 | -------------------------------------------------------------------------------- /docs/getting-started/step-1-configure.md: -------------------------------------------------------------------------------- 1 | # Configure 2 | 3 | ## Download kube-aws 4 | 5 | Go to the [releases](https://github.com/kubernetes-incubator/kube-aws/releases) and download the latest release tarball for your architecture. 6 | 7 | Extract the binary: 8 | 9 | ```sh 10 | tar zxvf kube-aws-${PLATFORM}.tar.gz 11 | ``` 12 | 13 | Add kube-aws to your path: 14 | 15 | ```sh 16 | mv ${PLATFORM}/kube-aws /usr/local/bin 17 | ``` 18 | 19 | ## Configure AWS credentials 20 | 21 | Configure your local workstation with AWS credentials using one of the following methods: 22 | 23 | ### Method 1: Configure command 24 | 25 | Provide the values of your AWS access and secret keys, and optionally default region and output format: 26 | 27 | ```sh 28 | $ aws configure 29 | AWS Access Key ID [None]: AKID1234567890 30 | AWS Secret Access Key [None]: MY-SECRET-KEY 31 | Default region name [None]: us-west-2 32 | Default output format [None]: text 33 | ``` 34 | 35 | ### Method 2: Config file 36 | 37 | Write your credentials into the file `~/.aws/credentials` using the following template: 38 | 39 | ``` 40 | [default] 41 | aws_access_key_id = AKID1234567890 42 | aws_secret_access_key = MY-SECRET-KEY 43 | ``` 44 | 45 | ### Method 3: Environment variables 46 | 47 | Provide AWS credentials to kube-aws by exporting the following environment variables: 48 | 49 | ```sh 50 | export AWS_ACCESS_KEY_ID=AKID1234567890 51 | export AWS_SECRET_ACCESS_KEY=MY-SECRET-KEY 52 | ``` 53 | 54 | ## Test Credentials 55 | 56 | Test that your credentials work by describing any instances you may already have running on your account: 57 | 58 | ```sh 59 | $ aws ec2 describe-instances 60 | ``` 61 | 62 | **Did you download kube-aws?** 63 | **Did your credentials work?** We will use the AWS CLI in the next step. 64 | 65 | [Yes, ready to configure my cluster options][getting-started-step-2] 66 | 67 | [No, I need more info on the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) 68 | 69 | [getting-started-step-1]: step-1-configure.md 70 | [getting-started-step-2]: step-2-render.md 71 | [getting-started-step-3]: step-3-launch.md 72 | [getting-started-step-4]: step-4-update.md 73 | [getting-started-step-5]: step-5-add-node-pool.md 74 | [getting-started-step-6]: step-6-configure-add-ons.md 75 | [getting-started-step-7]: step-7-destroy.md -------------------------------------------------------------------------------- /docs/getting-started/step-7-destroy.md: -------------------------------------------------------------------------------- 1 | ## Destroy the cluster 2 | 3 | When you are done with your cluster `kube-aws destroy` to destroy all the cluster components. It will as for confirmation. Use `--force` to skip confirmation step. 4 | 5 | If you created any Kubernetes Services of type `LoadBalancer`, you must delete these first, as the CloudFormation cannot be fully destroyed if any externally-managed resources still exist. 6 | -------------------------------------------------------------------------------- /docs/guides/README.md: -------------------------------------------------------------------------------- 1 | # Guides 2 | 3 | * [Operator Guide](operator-guide.md) for those performing day to day operations on a kube-aws generated cluster 4 | * [Developer Guide](developer-guide.md) for those wishing to join us in improving kube-aws 5 | -------------------------------------------------------------------------------- /docs/guides/operator-guide.md: -------------------------------------------------------------------------------- 1 | # Operator Guide 2 | 3 | Coming soon. -------------------------------------------------------------------------------- /docs/styles/site.css: -------------------------------------------------------------------------------- 1 | .gitbook-link { 2 | display: none !important; 3 | } 4 | -------------------------------------------------------------------------------- /docs/troubleshooting/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-retired/kube-aws/953e56eaaf1a04144f76c93dc8ceb0a3579c7953/docs/troubleshooting/README.md -------------------------------------------------------------------------------- /docs/troubleshooting/common-problems.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-retired/kube-aws/953e56eaaf1a04144f76c93dc8ceb0a3579c7953/docs/troubleshooting/common-problems.md -------------------------------------------------------------------------------- /docs/troubleshooting/known-limitations.md: -------------------------------------------------------------------------------- 1 | # Known Limitations 2 | 3 | ## hostPort doesn't work before Kubernetes 1.7.0 4 | 5 | This is a known issue with Kubernetes while using CNI with no available workaround for kube-aws. `hostPort` does not work if `hostNetwork: false`. 6 | 7 | If you want to deploy an Ingress controller such as `nginx-ingress-controller` which requires `hostPort`, just set `hostNetwork: true`: 8 | 9 | ```yaml 10 | spec: 11 | hostNetwork: true 12 | containers: 13 | - image: gcr.io/google_containers/nginx-ingress-controller:0.8.3 14 | name: nginx-ingress-lb 15 | ``` 16 | 17 | This is fixed in Kubernetes 1.7.0 and later. 18 | 19 | Relevant kube-aws issue: [does hostPort not work on kube-aws/CoreOS?](https://github.com/kubernetes-incubator/kube-aws/issues/91) 20 | 21 | See [the related upstream issue](https://github.com/kubernetes/kubernetes/issues/23920#issuecomment-254918942) for more information. 22 | 23 | This limitation is also documented in [the official Kubernetes doc](http://kubernetes.io/docs/admin/network-plugins/#cni). 24 | -------------------------------------------------------------------------------- /docs/tutorials/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-retired/kube-aws/953e56eaaf1a04144f76c93dc8ceb0a3579c7953/docs/tutorials/README.md -------------------------------------------------------------------------------- /e2e/kubernetes/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.8.3 2 | 3 | ARG KUBERNETES_VERSION=${KUBERNETES_VERSION:-v1.13.5} 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y rsync && \ 7 | go get -u github.com/jteeuwen/go-bindata/go-bindata && \ 8 | rm -rf /var/lib/apt/lists/* 9 | 10 | WORKDIR /go 11 | 12 | RUN git clone --depth 1 https://github.com/coreos/kubernetes -b $KUBERNETES_VERSION && \ 13 | cd kubernetes && \ 14 | git branch && \ 15 | git checkout -b $KUBERNETES_VERSION && \ 16 | git branch && \ 17 | make all WHAT=cmd/kubectl && \ 18 | find . -type f -executable -name kubectl && \ 19 | ./_output/local/bin/linux/amd64/kubectl version --client && \ 20 | make all WHAT=vendor/github.com/onsi/ginkgo/ginkgo && \ 21 | make all WHAT=test/e2e/e2e.test && \ 22 | rm -Rf .git 23 | 24 | ENV KUBERNETES_PROVIDER skeleton 25 | ENV KUBERNETES_CONFORMANCE_TEST Y 26 | 27 | ADD conformance.sh / 28 | 29 | WORKDIR /go/kubernetes 30 | 31 | CMD /conformance.sh 32 | -------------------------------------------------------------------------------- /e2e/kubernetes/Makefile: -------------------------------------------------------------------------------- 1 | KUBERNETES_VERSION ?= v1.14.1 2 | DOCKER_REPO ?= 3 | DOCKER_TAG ?= $(DOCKER_REPO)kube-e2e:$(KUBERNETES_VERSION) 4 | DOCKER_TAG_SANITIZED ?= $(shell echo $(DOCKER_TAG) | sed -e 's/+/_/') 5 | KUBE_AWS_ASSETS ?= /missing-kube-aws-assets 6 | SSH_PRIVATE_KEY ?= 7 | SSH_CMD ?= ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $(SSH_PRIVATE_KEY) 8 | MASTER_HOST ?= 9 | FOCUS ?= \[Conformance\] 10 | 11 | .PHONY: build 12 | build: 13 | docker build --build-arg KUBERNETES_VERSION=$(KUBERNETES_VERSION) -t $(DOCKER_TAG_SANITIZED) . 14 | 15 | .PHONY: publish 16 | publish: 17 | docker push $(DOCKER_TAG_SANITIZED) 18 | 19 | .PHONY: run-locally 20 | run-locally: 21 | docker run --rm -v $(KUBE_AWS_ASSETS):/kube $(DOCKER_TAG_SANITIZED) 22 | 23 | .PHONY: ssh 24 | ssh: 25 | ssh -i $(SSH_PRIVATE_KEY) core@$(MASTER_HOST) 26 | 27 | # example usage: 28 | # FOCUS='\[HPA\].*\[Conformance\]' DOCKER_REPO=quay.io/mumoshu/ MASTER_HOST=**.**.**.** SSH_PRIVATE_KEY=path/to/key KUBE_AWS_ASSETS=path/to/where/kube-aws/run sh -c 'make build && make publish && make run-remotely' 29 | # 30 | # FOCUS: 31 | # a regexp to selectively run e2e tests. Default: \[Conformance\] 32 | # see https://github.com/kubernetes/kubernetes/blob/master/docs/devel/e2e-tests.md#conformance-tests for more examples and description 33 | .PHONY: run-remotely 34 | run-remotely: 35 | rsync -a -e '$(SSH_CMD)' $(KUBE_AWS_ASSETS)/ core@$(MASTER_HOST):/home/core/kube 36 | $(SSH_CMD) core@$(MASTER_HOST) 'sh -c "docker pull $(DOCKER_TAG_SANITIZED) && container_id=$$(docker run -d -v /home/core/kube:/kube -e FOCUS='$(FOCUS)' $(DOCKER_TAG_SANITIZED)) && echo \$$container_id && docker logs -f \$$container_id"' 37 | 38 | .PHONY: get-log 39 | get-log: 40 | full_container_id=$$($(SSH_CMD) core@$(MASTER_HOST) 'docker ps -a --no-trunc | grep $(DOCKER_TAG_SANITIZED) | tee /dev/stderr | head -n 1 | awk "{ print \$$1 }"') && \ 41 | rsync --rsync-path="sudo rsync" -e '$(SSH_CMD)' core@$(MASTER_HOST):/var/lib/docker/containers/$${full_container_id}/$${full_container_id}-json.log . 42 | 43 | .PHONY: show-log 44 | show-log: 45 | $(SSH_CMD) core@$(MASTER_HOST) 'sh -c "container_id=$$(docker ps -a | grep $(DOCKER_TAG_SANITIZED) | head -n 1 | cut -d" " -f 1); docker logs -f \$$container_id"' 46 | 47 | .PHONY: sh 48 | sh: 49 | docker run -it --rm -v $(KUBE_AWS_ASSETS):/kube $(DOCKER_TAG_SANITIZED) sh 50 | -------------------------------------------------------------------------------- /e2e/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | `DOCKER_REPO=... make build publish` to build and then push the docker image with the latest, pre-built Kubernetes E2E test suite. 2 | -------------------------------------------------------------------------------- /e2e/kubernetes/conformance.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | if [ ! -d /kube ]; then 6 | echo /kube does not exist. run this docker container like '`docker run -v path-to-kube-assets:/kube image-name`' 1>&2 7 | exit 1 8 | fi 9 | 10 | DIR=/kube-temp 11 | 12 | mkdir ${DIR} 13 | 14 | cp -R /kube/* ${DIR}/ 15 | 16 | export KUBECONFIG=${DIR}/kubeconfig 17 | 18 | sed -i -e "s#credentials/#${DIR}/credentials/#g" ${KUBECONFIG} 19 | 20 | set -vx 21 | 22 | if [ "$FOCUS" != "" ]; then 23 | FOCUS=$(echo $FOCUS | sed -e 's/\[/\\[/' -e 's/\]/\\]/') 24 | fi 25 | 26 | FOCUS=${FOCUS:-\[Conformance\]} 27 | 28 | go run hack/e2e.go -v --test --check-version-skew=false --test_args="--ginkgo.focus=$FOCUS" 29 | -------------------------------------------------------------------------------- /filegen/filegen.go: -------------------------------------------------------------------------------- 1 | package filegen 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "text/template" 8 | ) 9 | 10 | func CreateFileFromTemplate(outputFilePath string, templateOpts interface{}, fileTemplate []byte) error { 11 | // Render the default cluster config. 12 | cfgTemplate, err := template.New("cluster.yaml").Parse(string(fileTemplate)) 13 | if err != nil { 14 | return fmt.Errorf("Error parsing default config template: %v", err) 15 | } 16 | 17 | dir := filepath.Dir(outputFilePath) 18 | 19 | if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { 20 | if err := os.MkdirAll(dir, 0755); err != nil { 21 | return fmt.Errorf("Error creating directory: %v", err) 22 | } 23 | } 24 | 25 | out, err := os.OpenFile(outputFilePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600) 26 | if err != nil { 27 | return fmt.Errorf("Error opening %s : %v", outputFilePath, err) 28 | } 29 | defer out.Close() 30 | if err := cfgTemplate.Execute(out, templateOpts); err != nil { 31 | return fmt.Errorf("Error exec-ing default config template: %v", err) 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /filegen/render.go: -------------------------------------------------------------------------------- 1 | package filegen 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | ) 8 | 9 | type file struct { 10 | name string 11 | data []byte 12 | mode os.FileMode 13 | } 14 | 15 | func File(name string, data []byte, mode os.FileMode) file { 16 | return file{ 17 | name: name, 18 | data: data, 19 | mode: mode, 20 | } 21 | } 22 | 23 | // Render writes all assets to disk. 24 | func Render(files ...file) error { 25 | for _, file := range files { 26 | if err := os.MkdirAll(path.Dir(file.name), 0755); err != nil { 27 | return err 28 | } 29 | 30 | if err := ioutil.WriteFile(file.name, file.data, file.mode); err != nil { 31 | return err 32 | } 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /filereader/jsontemplate/jsontemplate.go: -------------------------------------------------------------------------------- 1 | package jsontemplate 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/kubernetes-incubator/kube-aws/filereader/texttemplate" 8 | ) 9 | 10 | func GetBytes(filename string, data interface{}, prettyPrint bool) ([]byte, error) { 11 | rendered, err := texttemplate.GetString(filename, data) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | //Use unmarshal function to do syntax validation 17 | renderedBytes := []byte(rendered) 18 | var jsonHolder map[string]interface{} 19 | if err := json.Unmarshal(renderedBytes, &jsonHolder); err != nil { 20 | syntaxError, ok := err.(*json.SyntaxError) 21 | if ok { 22 | contextString := getContextString(renderedBytes, int(syntaxError.Offset), 10) 23 | return nil, fmt.Errorf("%v:\njson syntax error (offset=%d), in this region:\n-------\n%s\n-------\n", err, syntaxError.Offset, contextString) 24 | } 25 | return nil, err 26 | } 27 | 28 | // minify or pretty print JSON 29 | var buff bytes.Buffer 30 | err = nil 31 | if prettyPrint { 32 | err = json.Indent(&buff, renderedBytes, "", " ") 33 | } else { 34 | err = json.Compact(&buff, renderedBytes) 35 | } 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return buff.Bytes(), nil 41 | } 42 | 43 | func GetString(filename string, data interface{}, prettyPrint bool) (string, error) { 44 | bytes, err := GetBytes(filename, data, prettyPrint) 45 | 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | return string(bytes), nil 51 | } 52 | 53 | func getContextString(buf []byte, offset, lineCount int) string { 54 | // Prevent index out of range errors when we meet errors at the very end of json 55 | bufsize := len(buf) 56 | if offset >= bufsize { 57 | if bufsize > 0 { 58 | offset = bufsize - 1 59 | } else { 60 | offset = 0 61 | } 62 | } 63 | 64 | linesSeen := 0 65 | var leftLimit int 66 | for leftLimit = offset; leftLimit > 0 && linesSeen <= lineCount; leftLimit-- { 67 | if buf[leftLimit] == '\n' { 68 | linesSeen++ 69 | } 70 | } 71 | 72 | linesSeen = 0 73 | var rightLimit int 74 | for rightLimit = offset + 1; rightLimit < len(buf) && linesSeen <= lineCount; rightLimit++ { 75 | if buf[rightLimit] == '\n' { 76 | linesSeen++ 77 | } 78 | } 79 | 80 | return string(buf[leftLimit:rightLimit]) 81 | } 82 | -------------------------------------------------------------------------------- /filereader/texttemplate/texttemplate_test.go: -------------------------------------------------------------------------------- 1 | package texttemplate 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | var tolabelFunc = funcs2["toLabel"].(func(string) string) 10 | 11 | func TestCIDRToLabel(t *testing.T) { 12 | 13 | data := "192.168.0.0/16" 14 | label := tolabelFunc(data) 15 | 16 | assert.Equal(t, "192.168.0.0_16", label) 17 | } 18 | 19 | func TestMultipleSymbolsToReplace(t *testing.T) { 20 | 21 | data := "https://kubernetes.io/docs/admin/authorization/rbac/#referring-to-subjects" 22 | label := tolabelFunc(data) 23 | 24 | assert.Equal(t, "https___kubernetes.io_docs_admin_authorization_rbac__referring-to-subjects", label) 25 | } 26 | -------------------------------------------------------------------------------- /fingerprint/sha256.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | ) 7 | 8 | // SHA256 calculates and returns a SHA-256 hash intended to be used for a fingerprint of the original data 9 | func SHA256(data string) string { 10 | h := sha256.New() 11 | h.Write([]byte(data)) 12 | return fmt.Sprintf("%x", h.Sum(nil)) 13 | } 14 | 15 | func BytesToSha256(data []byte) []byte { 16 | h := sha256.New() 17 | h.Write([]byte(data)) 18 | return []byte(fmt.Sprintf("%x", h.Sum(nil))) 19 | } 20 | -------------------------------------------------------------------------------- /fingerprint/sha256_test.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSHA256(t *testing.T) { 8 | actual := SHA256("mychangingdata") 9 | expected := "ee867acc5d96cced9b9fe075e293604214519650065c60b42b95f1ccfbac2c97" 10 | if actual != expected { 11 | t.Errorf("unexpected value returned from SHA256: expected=%v actual=%v", expected, actual) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /flatcar/amiregistry/amiregistry.go: -------------------------------------------------------------------------------- 1 | package amiregistry 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | func GetAMI(region, channel string) (string, error) { 9 | 10 | amis, err := GetAMIData(channel) 11 | 12 | if err != nil { 13 | return "", fmt.Errorf("uanble to fetch AMI for channel \"%s\": %v", channel, err) 14 | } 15 | 16 | for _, v := range amis { 17 | if v["name"] != region { 18 | continue 19 | } 20 | if hvm, ok := v["hvm"]; ok { 21 | return hvm, nil 22 | } else { 23 | break 24 | } 25 | } 26 | 27 | return "", fmt.Errorf("could not find \"hvm\" image for region \"%s\" in flatcar channel \"%s\"", region, channel) 28 | } 29 | 30 | func GetAMIData(channel string) ([]map[string]string, error) { 31 | url := fmt.Sprintf("https://%s.release.flatcar-linux.net/amd64-usr/current/flatcar_production_ami_all.json", channel) 32 | r, err := newHttp().Get(url) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to get AMI data from url \"%s\": %v", channel, err) 35 | } 36 | 37 | if r.StatusCode != 200 { 38 | return nil, fmt.Errorf("failed to get AMI data from url \"%s\": invalid status code: %d", url, r.StatusCode) 39 | } 40 | 41 | output := map[string][]map[string]string{} 42 | 43 | err = json.NewDecoder(r.Body).Decode(&output) 44 | if err != nil { 45 | return nil, fmt.Errorf("failed to parse AMI data from url \"%s\": %v", url, err) 46 | } 47 | r.Body.Close() 48 | 49 | return output["amis"], nil 50 | } 51 | -------------------------------------------------------------------------------- /flatcar/amiregistry/reliable_http.go: -------------------------------------------------------------------------------- 1 | package amiregistry 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | const MaxRetryCount = 2 10 | 11 | type reliableHttp interface { 12 | Get(url string) (resp *http.Response, err error) 13 | } 14 | 15 | type reliableHttpImpl struct { 16 | underlyingGet func(url string) (resp *http.Response, err error) 17 | } 18 | 19 | func newHttp() reliableHttpImpl { 20 | return reliableHttpImpl{ 21 | underlyingGet: http.Get, 22 | } 23 | } 24 | 25 | func statusCodeIsRetryable(c int) bool { 26 | switch c { 27 | case 504, 520, 522: 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | // Get sends a HTTP GET request to the url. 34 | // If a request had been failed and its status code was one in th "retryable" status codes, the request is retried up to `MaxRetryCount` times. 35 | // The "retryable" status codes are defined in the `statusCodesIsRetryable` func. 36 | func (i reliableHttpImpl) Get(url string) (resp *http.Response, err error) { 37 | var r *http.Response 38 | var e error 39 | 40 | c := 0 41 | for c <= MaxRetryCount { 42 | c++ 43 | r, e = i.underlyingGet(url) 44 | 45 | if r != nil && statusCodeIsRetryable(r.StatusCode) { 46 | log.Printf("GET %s returned %d. retrying %d/%d", url, r.StatusCode, c, MaxRetryCount) 47 | } else if e == nil { 48 | return r, nil 49 | } 50 | 51 | if e != nil { 52 | log.Printf("GET %s failed due to \"%v\". retrying %d/%d", url, e, c, MaxRetryCount) 53 | } 54 | } 55 | 56 | return r, fmt.Errorf("max retry count exceeded: %v", e) 57 | } 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubernetes-incubator/kube-aws 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Masterminds/goutils v1.1.0 // indirect 7 | github.com/Masterminds/semver v1.4.2 8 | github.com/Masterminds/sprig v2.20.0+incompatible 9 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a 10 | github.com/aws/amazon-vpc-cni-k8s v1.5.3 11 | github.com/aws/aws-sdk-go v1.23.2 12 | github.com/coreos/coreos-cloudinit v1.14.0 13 | github.com/coreos/yaml v0.0.0-20141224210557-6b16a5714269 // indirect 14 | github.com/davecgh/go-spew v1.1.1 15 | github.com/go-yaml/yaml v2.1.0+incompatible 16 | github.com/gobuffalo/packr v0.0.0-20190628153553-9eb7a3d310e8 17 | github.com/google/go-cmp v0.3.0 18 | github.com/google/go-github v17.0.0+incompatible 19 | github.com/google/go-querystring v1.0.0 // indirect 20 | github.com/google/uuid v1.1.1 // indirect 21 | github.com/huandu/xstrings v1.2.0 // indirect 22 | github.com/imdario/mergo v0.3.7 23 | github.com/mattn/go-colorable v0.1.2 // indirect 24 | github.com/mattn/go-isatty v0.0.9 // indirect 25 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b 26 | github.com/pkg/errors v0.8.1 27 | github.com/prometheus/client_golang v1.1.0 // indirect 28 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect 29 | github.com/spf13/cobra v0.0.5 30 | github.com/stretchr/testify v1.4.0 31 | github.com/tidwall/gjson v1.3.2 // indirect 32 | github.com/tidwall/sjson v1.0.4 33 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 34 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 35 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 36 | gopkg.in/yaml.v2 v2.2.7 37 | ) 38 | -------------------------------------------------------------------------------- /gzipcompressor/gzipcompressor.go: -------------------------------------------------------------------------------- 1 | package gzipcompressor 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/base64" 7 | "fmt" 8 | "io/ioutil" 9 | ) 10 | 11 | func BytesToGzippedBytes(d []byte) ([]byte, error) { 12 | var buff bytes.Buffer 13 | gzw := gzip.NewWriter(&buff) 14 | if _, err := gzw.Write(d); err != nil { 15 | return []byte{}, err 16 | } 17 | if err := gzw.Close(); err != nil { 18 | return []byte{}, err 19 | } 20 | return buff.Bytes(), nil 21 | } 22 | 23 | func BytesToGzippedBase64String(d []byte) (string, error) { 24 | bytes, err := BytesToGzippedBytes(d) 25 | if err != nil { 26 | return "", err 27 | } 28 | return base64.StdEncoding.EncodeToString(bytes), nil 29 | } 30 | 31 | func StringToGzippedBase64String(str string) (string, error) { 32 | bytes := []byte(str) 33 | return BytesToGzippedBase64String(bytes) 34 | } 35 | 36 | func GzippedBase64StringToString(source string) (string, error) { 37 | b64Decoded, err := base64.StdEncoding.DecodeString(source) 38 | if err != nil { 39 | return "", fmt.Errorf("could not base 64 decode the data") 40 | } 41 | 42 | bread := bytes.NewReader(b64Decoded) 43 | gzr, _ := gzip.NewReader(bread) 44 | out, err := ioutil.ReadAll(gzr) 45 | if err != nil { 46 | return "", fmt.Errorf("Could not uncompress data") 47 | } 48 | return string(out), nil 49 | } 50 | -------------------------------------------------------------------------------- /hack/README.md: -------------------------------------------------------------------------------- 1 | ## Release note gathering 2 | 3 | To generate a release note for the release `v0.9.8-rc.1` in Markdown: 4 | 5 | 6 | The pre-requisite is to run `go get` several times to install dependencies: 7 | 8 | ``` 9 | $ go get -u "github.com/google/go-github/github" 10 | $ go get -u "golang.org/x/oauth2" 11 | $ go get -u "golang.org/x/context" 12 | ``` 13 | 14 | For a release note for a release-candidate version of kube-aws, run: 15 | 16 | ``` 17 | $ VERSION=v0.9.8-rc.1 go run relnotes.go 18 | ``` 19 | 20 | For one for a final version, run: 21 | 22 | ``` 23 | $ VERSION=v0.9.8 go run relnotes.go 24 | ``` 25 | -------------------------------------------------------------------------------- /hack/relnote: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | go get golang.org/x/oauth2 4 | go get golang.org/x/net/context 5 | go get github.com/google/go-github/github 6 | 7 | VERSION=$(hack/version) go run hack/relnote.go 8 | -------------------------------------------------------------------------------- /hack/version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | COMMIT=$(git rev-parse HEAD) 4 | TAG=$(git describe --exact-match --abbrev=0 --tags "${COMMIT}" 2> /dev/null || true) 5 | 6 | echo "${TAG}" 7 | -------------------------------------------------------------------------------- /kube-aws-bot-git-ssh-key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-retired/kube-aws/953e56eaaf1a04144f76c93dc8ceb0a3579c7953/kube-aws-bot-git-ssh-key.enc -------------------------------------------------------------------------------- /licenses/LICENSE.ebs-automatic-nvme-mapping: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Omachonu Ogali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /licenses/README.md: -------------------------------------------------------------------------------- 1 | This directory contains all the LICENSE files of the third-party softwares that kube-aws relies on: 2 | 3 | - [oogali/ebs-automatic-nvme-mapping](https://github.com/oogali/ebs-automatic-nvme-mapping/blob/master/LICENSE) 4 | 5 | Please see respective LICENSE files for licensing information. 6 | Also, we, the kube-aws team, greatly appretiate all the efforts made by the authors of those softwares. 7 | -------------------------------------------------------------------------------- /logger/color.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | const ( 4 | ColorNC = "\x1b[0m" // No Color 5 | ColorWhite = "\x1b[1;37m" 6 | ColorBlack = "\x1b[0;30m" 7 | ColorBlue = "\x1b[0;34m" 8 | ColorLightBlue = "\x1b[1;34m" 9 | ColorGreen = "\x1b[0;32m" 10 | ColorLightGreen = "\x1b[1;32m" 11 | ColorCyan = "\x1b[0;36m" 12 | ColorLightCyan = "\x1b[1;36m" 13 | ColorRed = "\x1b[0;31m" 14 | ColorLightRed = "\x1b[1;31m" 15 | ColorPurple = "\x1b[0;35m" 16 | ColorLightPurple = "\x1b[1;35m" 17 | ColorBrown = "\x1b[0;33m" 18 | ColorYellow = "\x1b[1;33m" 19 | ColorGrey = "\x1b[0;30m" 20 | ColorLightGrey = "\x1b[0;37m" 21 | ) 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "fmt" 7 | "github.com/kubernetes-incubator/kube-aws/cmd" 8 | ) 9 | 10 | func main() { 11 | if err := cmd.RootCmd.Execute(); err != nil { 12 | switch e := err.(type) { 13 | case *cmd.ExitError: 14 | fmt.Fprintf(os.Stderr, "%s\n", e.Error()) 15 | os.Exit(e.Code) 16 | } 17 | os.Exit(1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /make/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | printf 'Checking existence of the `tparse` command that is used for prettifying test results...' 5 | if ! which tparse 1>/dev/null; then 6 | echo not found. installing... 7 | go get -u github.com/mfridman/tparse 8 | echo 'Installed `tparse`.' 9 | else 10 | echo found. Skipped installation. 11 | fi 12 | 13 | default() { 14 | test -z "$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" 15 | go test -json -v $(go list ./... | grep -v '/vendor/' | grep -v '/hack') | tparse 16 | go vet $(go list ./... | grep -v '/vendor/' | grep -v '/hack') 17 | } 18 | 19 | with-cover() { 20 | test -z "$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" 21 | for d in $(go list ./... | grep -v '/vendor/' | grep -v '/hack'); do 22 | go test -timeout 35m -json -v --race -coverprofile=profile.out -covermode=atomic $d | tparse 23 | if [ -f profile.out ]; then 24 | cat profile.out >> coverage.txt 25 | rm profile.out 26 | fi 27 | done 28 | go vet $(go list ./... | grep -v '/vendor/' | grep -v '/hack') 29 | } 30 | 31 | if [ -z ${1+x} ]; then 32 | default 33 | else 34 | "$@" 35 | fi 36 | -------------------------------------------------------------------------------- /naming/convention.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func FromStackToCfnResource(stackName string) string { 8 | // Convert stack name into something valid as a cfn resource name or 9 | // we'll end up with cfn errors like "Template format error: Resource name test5-controlplane is non alphanumeric" 10 | return strings.Title(strings.Replace(stackName, "-", "", -1)) 11 | } 12 | -------------------------------------------------------------------------------- /netutil/netutil.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import "net" 4 | 5 | //Does the address space of these networks "a" and "b" overlap? 6 | func CidrOverlap(a, b *net.IPNet) bool { 7 | return a.Contains(b.IP) || b.Contains(a.IP) 8 | } 9 | 10 | //Return next IP address in network range 11 | func IncrementIP(netIP net.IP) net.IP { 12 | ip := make(net.IP, len(netIP)) 13 | copy(ip, netIP) 14 | 15 | for j := len(ip) - 1; j >= 0; j-- { 16 | ip[j]++ 17 | if ip[j] > 0 { 18 | break 19 | } 20 | } 21 | 22 | return ip 23 | } 24 | -------------------------------------------------------------------------------- /pkg/api/addons.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Addons struct { 4 | Rescheduler Rescheduler `yaml:"rescheduler"` 5 | MetricsServer MetricsServer `yaml:"metricsServer,omitempty"` 6 | Prometheus Prometheus `yaml:"prometheus"` 7 | APIServerAggregator APIServerAggregator `yaml:"apiserverAggregator"` 8 | UnknownKeys `yaml:",inline"` 9 | } 10 | 11 | type Rescheduler struct { 12 | Enabled bool `yaml:"enabled"` 13 | UnknownKeys `yaml:",inline"` 14 | } 15 | 16 | type MetricsServer struct { 17 | Enabled bool `yaml:"enabled"` 18 | UnknownKeys `yaml:",inline"` 19 | } 20 | 21 | type Prometheus struct { 22 | SecurityGroupsEnabled bool `yaml:"securityGroupsEnabled"` 23 | UnknownKeys `yaml:",inline"` 24 | } 25 | 26 | type APIServerAggregator struct { 27 | Enabled bool `yaml:"enabled"` 28 | } 29 | -------------------------------------------------------------------------------- /pkg/api/amazon_vpc.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/aws/amazon-vpc-cni-k8s/pkg/awsutils" 6 | "github.com/kubernetes-incubator/kube-aws/provisioner" 7 | ) 8 | 9 | type AmazonVPC struct { 10 | Enabled bool `yaml:"enabled"` 11 | } 12 | 13 | func (a AmazonVPC) MaxPodsScript() provisioner.Content { 14 | script := `#!/usr/bin/env bash 15 | 16 | set -e 17 | 18 | declare -A instance_eni_available 19 | ` 20 | 21 | for it, num := range awsutils.InstanceENIsAvailable { 22 | script = script + fmt.Sprintf(`instance_eni_available["%s"]=%d 23 | `, it, num) 24 | } 25 | 26 | script = script + ` 27 | declare -A instance_ip_available 28 | ` 29 | for it, num := range awsutils.InstanceIPsAvailable { 30 | script = script + fmt.Sprintf(`instance_ip_available["%s"]=%d 31 | `, it, num) 32 | } 33 | 34 | script = script + ` 35 | 36 | instance_type=$(curl http://169.254.169.254/latest/meta-data/instance-type) 37 | 38 | enis=${instance_eni_available["$instance_type"]} 39 | 40 | if [ "" == "$enis" ]; then 41 | echo "unsupported instance type: no enis_per_eni defined: $instance_type" 1>&2 42 | exit 1 43 | fi 44 | 45 | # According to https://github.com/aws/amazon-vpc-cni-k8s#eni-allocation 46 | ips_per_eni=${instance_ip_available["$instance_type"]} 47 | 48 | if [ "" == "$ips_per_eni" ]; then 49 | echo "unsupported instance type: no ips_per_eni defined: $instance_type" 1>&2 50 | exit 1 51 | fi 52 | 53 | max_pods=$(( (enis * (ips_per_eni - 1)) + 2 )) 54 | 55 | printf $max_pods 56 | ` 57 | return provisioner.NewBinaryContent([]byte(script)) 58 | } 59 | -------------------------------------------------------------------------------- /pkg/api/api_endpoint.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // APIEndpoint is a Kubernetes API endpoint to which various clients connect. 9 | // Each endpoint can be served by an existing ELB or a kube-aws managed ELB. 10 | type APIEndpoint struct { 11 | // Name is the unique name of this API endpoint used by kube-aws for identifying this API endpoint 12 | Name string `yaml:"name,omitempty"` 13 | // DNSName is the FQDN of this endpoint 14 | // A record set may or may not be created with this DNS name. 15 | // TLS certificates generated by kube-aws would contain this name in the list of common names. 16 | DNSName string `yaml:"dnsName,omitempty"` 17 | // LoadBalancer is a set of an ELB and relevant settings and resources to serve a Kubernetes API hosted by controller nodes 18 | LoadBalancer APIEndpointLB `yaml:"loadBalancer,omitempty"` 19 | //DNSRoundRobin APIDNSRoundRobin `yaml:"dnsRoundRobin,omitempty"` 20 | UnknownKeys `yaml:",inline"` 21 | } 22 | 23 | // Validate returns an error when there's any user error in the `apiEndpoint` settings 24 | func (e APIEndpoint) Validate() error { 25 | if err := e.LoadBalancer.Validate(); err != nil { 26 | return fmt.Errorf("invalid loadBalancer: %v", err) 27 | } 28 | if e.DNSName == "" { 29 | return errors.New("dnsName must be set") 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/api/asg.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Configuration specific to auto scaling groups 8 | type AutoScalingGroup struct { 9 | MinSize *int `yaml:"minSize,omitempty"` 10 | MaxSize int `yaml:"maxSize,omitempty"` 11 | RollingUpdateMinInstancesInService *int `yaml:"rollingUpdateMinInstancesInService,omitempty"` 12 | MixedInstances MixedInstances `yaml:"mixedInstances,omitempty"` 13 | UnknownKeys `yaml:",inline"` 14 | } 15 | 16 | func (asg AutoScalingGroup) Validate() error { 17 | if asg.MinSize != nil && *asg.MinSize < 0 { 18 | return fmt.Errorf("`autoScalingGroup.minSize` must be zero or greater if specified") 19 | } 20 | if asg.MaxSize < 0 { 21 | return fmt.Errorf("`autoScalingGroup.maxSize` must be zero or greater if specified") 22 | } 23 | if asg.MinSize != nil && *asg.MinSize > asg.MaxSize { 24 | return fmt.Errorf("`autoScalingGroup.minSize` (%d) must be less than or equal to `autoScalingGroup.maxSize` (%d), if you have specified only minSize please specify maxSize as well", 25 | *asg.MinSize, asg.MaxSize) 26 | } 27 | if asg.RollingUpdateMinInstancesInService != nil && *asg.RollingUpdateMinInstancesInService < 0 { 28 | return fmt.Errorf("`autoScalingGroup.rollingUpdateMinInstancesInService` must be greater than or equal to 0 but was %d", *asg.RollingUpdateMinInstancesInService) 29 | } 30 | if asg.MixedInstances.Enabled { 31 | return asg.MixedInstances.Validate() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/api/assets.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubernetes-incubator/kube-aws/fingerprint" 6 | "strings" 7 | ) 8 | 9 | type AssetID struct { 10 | StackName string 11 | Filename string 12 | } 13 | 14 | type Asset struct { 15 | AssetLocation 16 | Content string 17 | } 18 | 19 | type AssetLocation struct { 20 | ID AssetID 21 | Key string 22 | Bucket string 23 | Path string 24 | Region Region 25 | } 26 | 27 | func NewAssetID(stack string, file string) AssetID { 28 | return AssetID{ 29 | StackName: stack, 30 | Filename: file, 31 | } 32 | } 33 | 34 | func (l AssetLocation) URL() (string, error) { 35 | if (l == AssetLocation{}) { 36 | return "", fmt.Errorf("[bug] Empty asset location can't have URL") 37 | } 38 | return fmt.Sprintf("%s/%s/%s", l.Region.S3Endpoint(), l.Bucket, l.Key), nil 39 | } 40 | 41 | func (l AssetLocation) S3URL() (string, error) { 42 | if (l == AssetLocation{}) { 43 | return "", fmt.Errorf("[bug] Empty asset location can't have S3 URL") 44 | } 45 | return fmt.Sprintf("s3://%s/%s", l.Bucket, l.Key), nil 46 | } 47 | 48 | func (l Asset) S3Prefix() (string, error) { 49 | if (l.AssetLocation == AssetLocation{}) { 50 | return "", fmt.Errorf("[bug] Empty asset location can't have URL") 51 | } 52 | prefix := strings.TrimSuffix(l.Key, fmt.Sprintf("-%s", fingerprint.SHA256(l.Content))) 53 | return fmt.Sprintf("%s/%s", l.Bucket, prefix), nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/api/aws_iam.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type KubernetesAuthentication struct { 4 | AWSIAM AWSIAM `yaml:"awsIAM"` 5 | } 6 | 7 | type AWSIAM struct { 8 | Enabled bool `yaml:"enabled"` 9 | BinaryDownloadURL string `yaml:"binaryDownloadURL"` 10 | ClusterID string `yaml:"clusterID"` 11 | } 12 | 13 | func (a AWSIAM) BinaryStorePathes() []string { 14 | return []string{ 15 | "files/worker/opt/bin/aws-iam-authenticator", 16 | "files/controller/opt/bin/aws-iam-authenticator", 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/api/bash_prompt.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type BashPrompt struct { 4 | Enabled bool `yaml:"enabled,omitempty"` 5 | IncludePWD bool `yaml:"include-pwd,omitempty"` 6 | IncludeHostname bool `yaml:"include-hostname,omitempty"` 7 | IncludeUser bool `yaml:"include-user,omitempty"` 8 | ClusterColour ShellColour `yaml:"cluster-colour,omitempty"` 9 | Divider string `yaml:"divider,omitempty"` 10 | DividerColour ShellColour `yaml:"divider-colour,omitempty"` 11 | EtcdLabel string `yaml:"etcd-label,omitempty"` 12 | EtcdColour ShellColour `yaml:"etcd-colour,omitempty"` 13 | ControllerLabel string `yaml:"controller-label,omitempty"` 14 | ControllerColour ShellColour `yaml:"controller-colour,omitempty"` 15 | WorkerLabel string `yaml:"worker-label,omitempty"` 16 | WorkerColour ShellColour `yaml:"worker-colour,omitempty"` 17 | RootUserColour ShellColour `yaml:"root-user-colour,omitempty"` 18 | NonRootUserColour ShellColour `yaml:"non-root-user-colour,omitempty"` 19 | DirectoryColour ShellColour `yaml:"directory-colour,omitempty"` 20 | } 21 | 22 | func NewDefaultBashPrompt() BashPrompt { 23 | return BashPrompt{ 24 | Enabled: true, 25 | IncludePWD: true, 26 | IncludeHostname: true, 27 | IncludeUser: true, 28 | ClusterColour: LightCyan, 29 | Divider: "|", 30 | DividerColour: DefaultColour, 31 | EtcdLabel: "etcd", 32 | EtcdColour: LightGreen, 33 | ControllerLabel: "master", 34 | ControllerColour: LightRed, 35 | WorkerLabel: "node", 36 | WorkerColour: LightBlue, 37 | RootUserColour: LightRed, 38 | NonRootUserColour: LightGreen, 39 | DirectoryColour: LightBlue, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/api/cidr_range.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // CIDRRanges represents IP network ranges in CIDR notation 9 | type CIDRRanges []CIDRRange 10 | 11 | // CIDRRange represents an IP network range in CIDR notation 12 | // See http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html#cfn-ec2-security-group-ingress-cidrip 13 | type CIDRRange struct { 14 | str string 15 | } 16 | 17 | func DefaultCIDRRanges() CIDRRanges { 18 | return CIDRRanges{ 19 | {"0.0.0.0/0"}, 20 | } 21 | } 22 | 23 | func (c *CIDRRange) UnmarshalYAML(unmarshal func(interface{}) error) error { 24 | var cidr string 25 | if err := unmarshal(&cidr); err != nil { 26 | return fmt.Errorf("failed to parse CIDR range: %v", err) 27 | } 28 | 29 | _, _, err := net.ParseCIDR(cidr) 30 | if err != nil { 31 | return fmt.Errorf("failed to parse CIDR range: %v", err) 32 | } 33 | 34 | *c = CIDRRange{str: cidr} 35 | 36 | return nil 37 | } 38 | 39 | // String returns the string representation of this CIDR range 40 | func (c CIDRRange) String() string { 41 | return c.str 42 | } 43 | -------------------------------------------------------------------------------- /pkg/api/cidr_range_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-yaml/yaml" 6 | "testing" 7 | ) 8 | 9 | func TestCIDRRangesExtractFromYAML(t *testing.T) { 10 | t.Run("WhenOverrodeWithNonEmpty", func(t *testing.T) { 11 | rs := struct { 12 | CIDRRanges `yaml:"rs"` 13 | }{DefaultCIDRRanges()} 14 | err := yaml.Unmarshal([]byte("rs:\n- \"1.2.3.255/32\"\n"), &rs) 15 | if err != nil { 16 | t.Errorf("failed ot extract CIDR ranges from yaml: %v", err) 17 | t.FailNow() 18 | } 19 | expected := "1.2.3.255/32" 20 | actual := rs.CIDRRanges[0].str 21 | if actual != expected { 22 | t.Errorf("unexpected cidr range extracted. expected = %s, actual = %s", expected, actual) 23 | } 24 | }) 25 | t.Run("WhenOverrodeWithEmpty", func(t *testing.T) { 26 | rs := struct { 27 | CIDRRanges `yaml:"rs"` 28 | }{DefaultCIDRRanges()} 29 | err := yaml.Unmarshal([]byte("rs:\n"), &rs) 30 | if err != nil { 31 | t.Errorf("failed ot extract CIDR ranges from yaml: %v", err) 32 | t.FailNow() 33 | } 34 | if len(rs.CIDRRanges) != 0 { 35 | t.Errorf("unexpected cidr ranges to be empty, but was: %+v(len=%d)", rs.CIDRRanges, len(rs.CIDRRanges)) 36 | } 37 | }) 38 | t.Run("WhenOmitted", func(t *testing.T) { 39 | rs := struct { 40 | CIDRRanges `yaml:"rs"` 41 | }{DefaultCIDRRanges()} 42 | err := yaml.Unmarshal([]byte(""), &rs) 43 | if err != nil { 44 | t.Errorf("failed ot extract CIDR ranges from yaml: %v", err) 45 | t.FailNow() 46 | } 47 | expected := "0.0.0.0/0" 48 | actual := rs.CIDRRanges[0].str 49 | if actual != expected { 50 | t.Errorf("unexpected cidr range extracted. expected = %s, actual = %s", expected, actual) 51 | } 52 | }) 53 | } 54 | 55 | func TestCIDRRangeExtractFromYAML(t *testing.T) { 56 | r := CIDRRange{} 57 | err := yaml.Unmarshal([]byte("\"0.0.0.0/0\""), &r) 58 | if err != nil { 59 | t.Errorf("failed to extract CIDR range from yaml: %v", err) 60 | } 61 | expected := "0.0.0.0/0" 62 | if r.str != expected { 63 | t.Errorf("unexpected cidr range extracted. expected = %s, actual = %s", expected, r.str) 64 | } 65 | } 66 | 67 | func TestCIDRRangeString(t *testing.T) { 68 | r := CIDRRange{str: "0.0.0.0/0"} 69 | expected := "0.0.0.0/0" 70 | actual := fmt.Sprintf("%s", r) 71 | if actual != expected { 72 | t.Errorf("unexpected string rendered. expected = %s, actual = %s", expected, actual) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/api/cloudformation.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type CloudFormation struct { 4 | RoleARN string `yaml:"roleARN,omitempty"` 5 | StackNameOverrides StackNameOverrides `yaml:"stackNameOverrides,omitempty"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/api/cloudwatch_logging.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type SystemdMessageResponse struct { 4 | InstanceId string `json:"instanceId,omitempty"` 5 | Hostname string `json:"hostname,omitempty"` 6 | CmdName string `json:"cmdName,omitempty"` 7 | Exe string `json:"exe,omitempty"` 8 | CmdLine string `json:"cmdLine,omitempty"` 9 | SystemdUnit string `json:"systemdUnit,omitempty"` 10 | Priority string `json:"priority,omitempty"` 11 | Message string `json:"message,omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /pkg/api/const.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | var ( 4 | // ControlPlaneStackName is the logical name of a CloudFormation stack resource in a root stack template 5 | // This is not needed to be unique in an AWS account because the actual name of a nested stack is generated randomly 6 | // by CloudFormation by including the logical name. 7 | // This is NOT intended to be used to reference stack name from cloud-config as the target of awscli or cfn-bootstrap-tools commands e.g. `cfn-init` and `cfn-signal` 8 | controlPlaneStackName = "control-plane" 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/api/custom_systemd_unit.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | type CustomSystemdUnit struct { 9 | Name string `yaml:"name"` 10 | Command string `yaml:"command,omitempty"` 11 | Content string `yaml:"content,omitempty"` 12 | Enable bool `yaml:"enable,omitempty"` 13 | Runtime bool `yaml:"runtime,omitempty"` 14 | DropIns []CustomSystemdUnitDropIn `yaml:"drop-ins,omitempty"` 15 | UnknownKeys `yaml:",inline"` 16 | } 17 | 18 | func (c CustomSystemdUnit) ContentPresent() bool { 19 | if len(c.Content) > 0 { 20 | return true 21 | } 22 | return false 23 | } 24 | 25 | func (c CustomSystemdUnit) DropInsPresent() bool { 26 | if len(c.DropIns) > 0 { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func (c CustomSystemdUnit) ContentArray() []string { 33 | trimmedContent := strings.TrimRight(c.Content, "\n") 34 | return strings.Split(trimmedContent, "\n") 35 | } 36 | 37 | func (c CustomSystemdUnit) EnableString() string { 38 | return strconv.FormatBool(c.Enable) 39 | } 40 | 41 | func (c CustomSystemdUnit) RuntimeString() string { 42 | return strconv.FormatBool(c.Runtime) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/api/custom_systemd_unit_dropin.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type CustomSystemdUnitDropIn struct { 8 | Name string `yaml:"name"` 9 | Content string `yaml:"content"` 10 | } 11 | 12 | func (c CustomSystemdUnitDropIn) ContentArray() []string { 13 | trimmedContent := strings.TrimRight(c.Content, "\n") 14 | return strings.Split(trimmedContent, "\n") 15 | } 16 | -------------------------------------------------------------------------------- /pkg/api/data_volume.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type DataVolume struct { 4 | Size int `yaml:"size,omitempty"` 5 | Type string `yaml:"type,omitempty"` 6 | IOPS int `yaml:"iops,omitempty"` 7 | Ephemeral bool `yaml:"ephemeral,omitempty"` 8 | Encrypted bool `yaml:"encrypted,omitempty"` 9 | UnknownKeys `yaml:",inline"` 10 | } 11 | -------------------------------------------------------------------------------- /pkg/api/ec2_instance.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "strings" 4 | 5 | type EC2Instance struct { 6 | Count int `yaml:"count,omitempty"` 7 | CreateTimeout string `yaml:"createTimeout,omitempty"` 8 | InstanceType string `yaml:"instanceType,omitempty"` 9 | RootVolume `yaml:"rootVolume,omitempty"` 10 | Tenancy string `yaml:"tenancy,omitempty"` 11 | InstanceTags map[string]string `yaml:"instanceTags,omitempty"` 12 | } 13 | 14 | var nvmeEC2InstanceFamily = []string{"c5", "m5"} 15 | 16 | func isNvmeEC2InstanceType(instanceType string) bool { 17 | for _, family := range nvmeEC2InstanceFamily { 18 | if strings.HasPrefix(instanceType, family) { 19 | return true 20 | } 21 | } 22 | return false 23 | } 24 | 25 | // This function is used when rendering cloud-config-worker 26 | func (e EC2Instance) HasNvmeDevices() bool { 27 | return isNvmeEC2InstanceType(e.InstanceType) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/api/etcd_node.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type EtcdNode struct { 4 | Name string `yaml:"name,omitempty"` 5 | FQDN string `yaml:"fqdn,omitempty"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/api/etcd_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEtcd(t *testing.T) { 8 | etcdTest := Etcd{ 9 | EC2Instance: EC2Instance{ 10 | Count: 1, 11 | InstanceType: "t2.medium", 12 | RootVolume: RootVolume{ 13 | Size: 30, 14 | Type: "gp2", 15 | IOPS: 0, 16 | }, 17 | Tenancy: "default", 18 | }, 19 | DataVolume: DataVolume{ 20 | Size: 30, 21 | Type: "gp2", 22 | IOPS: 0, 23 | }, 24 | StackExists: false, 25 | UserSuppliedArgs: UserSuppliedArgs{ 26 | QuotaBackendBytes: 100000000, 27 | AutoCompactionRetention: 1, 28 | }, 29 | } 30 | 31 | if etcdTest.LogicalName() != "Etcd" { 32 | t.Errorf("logical name incorrect, expected: Etcd, got: %s", etcdTest.LogicalName()) 33 | } 34 | 35 | if etcdTest.NameTagKey() != "kube-aws:etcd:name" { 36 | t.Errorf("name tag key incorrect, expected: kube-aws:etcd:name, got: %s", etcdTest.NameTagKey()) 37 | } 38 | 39 | if !etcdTest.NodeShouldHaveEIP() { 40 | t.Error("expected: true, got: false") 41 | } 42 | 43 | if etcdTest.SecurityGroupRefs()[0] != `{"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStackName}-EtcdSecurityGroup"}}` { 44 | t.Errorf("etcd security group refs incorrect, expected: `{'Fn::ImportValue' : {'Fn::Sub' : '${NetworkStackName}-EtcdSecurityGroup'}}`, got: %s", etcdTest.SecurityGroupRefs()[0]) 45 | } 46 | 47 | if err := etcdTest.Validate(); err != nil { 48 | t.Error(err) 49 | } 50 | 51 | if etcdTest.FormatOpts() != "--quota-backend-bytes=100000000 --auto-compaction-retention=1" { 52 | t.Errorf("etcd optional args incorrect, expected `--quota-backend-bytes=100000000 --auto-compaction-retention=1`, got: `%s`", etcdTest.FormatOpts()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/api/existing_etcd.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ExistingState describes the existing state of the etcd cluster 4 | type EtcdExistingState struct { 5 | StackExists bool 6 | EtcdMigrationEnabled bool 7 | EtcdMigrationExistingEndpoints string 8 | } 9 | -------------------------------------------------------------------------------- /pkg/api/feature_gates.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strconv" 7 | "strings" 8 | 9 | yaml "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type FeatureGates map[string]string 13 | 14 | func (l FeatureGates) Enabled() bool { 15 | return len(l) > 0 16 | } 17 | 18 | // Returns key=value pairs separated by ',' to be passed to kubelet's `--feature-gates` flag 19 | func (l FeatureGates) String() string { 20 | labels := []string{} 21 | keys := []string{} 22 | for k, _ := range l { 23 | keys = append(keys, k) 24 | } 25 | sort.Strings(keys) 26 | for _, k := range keys { 27 | v := l[k] 28 | if len(v) > 0 { 29 | labels = append(labels, fmt.Sprintf("%s=%s", k, v)) 30 | } else { 31 | labels = append(labels, fmt.Sprintf("%s", k)) 32 | } 33 | } 34 | return strings.Join(labels, ",") 35 | } 36 | 37 | // Convert the map[string]string FeatureGates to a map[string]bool yaml representation 38 | func (l FeatureGates) Yaml() (string, error) { 39 | outmap := make(map[string]bool) 40 | var err error 41 | for k, v := range l { 42 | outmap[k], err = strconv.ParseBool(v) 43 | if err != nil { 44 | return "", err 45 | } 46 | } 47 | out, err := yaml.Marshal(&outmap) 48 | return string(out), err 49 | } 50 | -------------------------------------------------------------------------------- /pkg/api/gpu.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/kubernetes-incubator/kube-aws/logger" 7 | "strings" 8 | ) 9 | 10 | var GPUEnabledInstanceFamily = []string{"p2", "p3", "g2", "g3"} 11 | 12 | type Gpu struct { 13 | Nvidia NvidiaSetting `yaml:"nvidia"` 14 | } 15 | 16 | type NvidiaSetting struct { 17 | Enabled bool `yaml:"enabled,omitempty"` 18 | Version string `yaml:"version,omitempty"` 19 | } 20 | 21 | func isGpuEnabledInstanceType(instanceType string) bool { 22 | for _, family := range GPUEnabledInstanceFamily { 23 | if strings.HasPrefix(instanceType, family) { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | func newDefaultGpu() Gpu { 31 | return Gpu{ 32 | Nvidia: NvidiaSetting{ 33 | Enabled: false, 34 | Version: "", 35 | }, 36 | } 37 | } 38 | 39 | // This function is used when rendering cloud-config-worker 40 | func (c NvidiaSetting) IsEnabledOn(instanceType string) bool { 41 | return isGpuEnabledInstanceType(instanceType) && c.Enabled 42 | } 43 | 44 | func (c Gpu) Validate(instanceType string, experimentalGpuSupportEnabled bool) error { 45 | if c.Nvidia.Enabled && !isGpuEnabledInstanceType(instanceType) { 46 | return errors.New(fmt.Sprintf("instance type %v doesn't support GPU. You can enable Nvidia driver intallation support only when use %v instance family.", instanceType, GPUEnabledInstanceFamily)) 47 | } 48 | if !c.Nvidia.Enabled && !experimentalGpuSupportEnabled && isGpuEnabledInstanceType(instanceType) { 49 | logger.Warnf("Nvidia GPU driver intallation is disabled although instance type %v does support GPU. You have to install Nvidia GPU driver by yourself to schedule gpu resource.\n", instanceType) 50 | } 51 | if c.Nvidia.Enabled && experimentalGpuSupportEnabled { 52 | return errors.New(`Only one of gpu.nvidia.enabled and experimental.gpuSupport.enabled are allowed at one time.`) 53 | } 54 | if c.Nvidia.Enabled && len(c.Nvidia.Version) == 0 { 55 | return errors.New(`gpu.nvidia.version must not be empty when gpu.nvidia is enabled.`) 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/api/helm_release_fileset.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/kubernetes-incubator/kube-aws/provisioner" 4 | 5 | type HelmReleaseFileset struct { 6 | ValuesFile *provisioner.RemoteFile 7 | ReleaseFile *provisioner.RemoteFile 8 | } 9 | -------------------------------------------------------------------------------- /pkg/api/hosted_zone.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // HostedZone is a AWS Route 53 hosted zone in which record sets are created. 4 | // Record sts are created to register DNS records to make various DNS names of nodes and/or load LBs managed by kube-aws 5 | // visible to an internal network or the internet 6 | type HostedZone struct { 7 | // Identifier should include the hosted zone ID for a private or public hosted zone, 8 | // to make DNS names available to an internal network or the internet respectively 9 | Identifier `yaml:",inline"` 10 | } 11 | -------------------------------------------------------------------------------- /pkg/api/identifier.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type Identifier struct { 9 | ID string `yaml:"id,omitempty"` 10 | IDFromStackOutput string `yaml:"idFromStackOutput,omitempty"` 11 | IDFromFn string `yaml:"idFromFn,omitempty"` 12 | } 13 | 14 | // HasIdentifier returns true when the id of a resource i.e. either `id` or `idFromStackOutput` is specified 15 | func (i Identifier) HasIdentifier() bool { 16 | return i.ID != "" || i.IDFromStackOutput != "" 17 | } 18 | 19 | func (i Identifier) Validate() error { 20 | if i.IDFromFn != "" { 21 | var jsonHolder map[string]interface{} 22 | if err := json.Unmarshal([]byte(i.IDFromFn), &jsonHolder); err != nil { 23 | return fmt.Errorf("idFromFn must be a valid json expression but was not: %s", i.IDFromFn) 24 | } 25 | } 26 | return nil 27 | } 28 | 29 | func (i Identifier) Ref(logicalNameProvider func() string) string { 30 | if i.IDFromStackOutput != "" { 31 | return fmt.Sprintf(`{ "Fn::ImportValue" : %q }`, i.IDFromStackOutput) 32 | } else if i.ID != "" { 33 | return fmt.Sprintf(`"%s"`, i.ID) 34 | } else if i.IDFromFn != "" { 35 | return i.IDFromFn 36 | } else { 37 | return fmt.Sprintf(`{ "Ref" : %q }`, logicalNameProvider()) 38 | } 39 | } 40 | 41 | // RefOrError should be used instead of Ref where possible so that kube-aws can print a more useful error message with 42 | // the line number for the stack-template.json when there's an error. 43 | func (i Identifier) RefOrError(logicalNameProvider func() (string, error)) (string, error) { 44 | if i.IDFromStackOutput != "" { 45 | return fmt.Sprintf(`{ "Fn::ImportValue" : %q }`, i.IDFromStackOutput), nil 46 | } else if i.ID != "" { 47 | return fmt.Sprintf(`"%s"`, i.ID), nil 48 | } else if i.IDFromFn != "" { 49 | return i.IDFromFn, nil 50 | } else { 51 | logicalName, err := logicalNameProvider() 52 | if err != nil { 53 | return "", fmt.Errorf("failed to get id or ref: %v", err) 54 | } 55 | return fmt.Sprintf(`{ "Ref" : %q }`, logicalName), nil 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/api/image.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Image struct { 8 | Repo string `yaml:"repo,omitempty"` 9 | RktPullDocker bool `yaml:"rktPullDocker,omitempty"` 10 | Tag string `yaml:"tag,omitempty"` 11 | } 12 | 13 | func (i *Image) MergeIfEmpty(other Image) { 14 | if i.Repo == "" || i.Tag == "" { 15 | i.Repo = other.Repo 16 | i.Tag = other.Tag 17 | i.RktPullDocker = other.RktPullDocker 18 | } 19 | } 20 | 21 | func (i *Image) Options() string { 22 | if i.RktPullDocker { 23 | return "--insecure-options=image " 24 | } 25 | return "" 26 | } 27 | 28 | func (i *Image) RktRepo() string { 29 | if i.RktPullDocker { 30 | return fmt.Sprintf("docker://%s:%s", i.Repo, i.Tag) 31 | } 32 | return fmt.Sprintf("%s:%s", i.Repo, i.Tag) 33 | } 34 | 35 | func (i *Image) RktRepoWithoutTag() string { 36 | if i.RktPullDocker { 37 | return fmt.Sprintf("docker://%s", i.Repo) 38 | } 39 | return i.Repo 40 | } 41 | 42 | func (i *Image) RepoWithTag() string { 43 | return fmt.Sprintf("%s:%s", i.Repo, i.Tag) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/api/internet_gateway.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type InternetGateway struct { 4 | Identifier `yaml:",inline"` 5 | } 6 | 7 | func (g InternetGateway) ManageInternetGateway() bool { 8 | return !g.HasIdentifier() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/api/keypair_spec.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "time" 7 | ) 8 | 9 | type KeyPairSpec struct { 10 | Name string `yaml:"name"` 11 | CommonName string `yaml:"commonName"` 12 | Organization string `yaml:"organization"` 13 | Duration time.Duration `yaml:"duration"` 14 | DNSNames []string `yaml:"dnsNames"` 15 | IPAddresses []string `yaml:"ipAddresses"` 16 | Usages []string `yaml:"usages"` 17 | // Signer is the name of the keypair for the private key used to sign the cert 18 | Signer string `yaml:"signer"` 19 | } 20 | 21 | func (spec KeyPairSpec) EncryptedKeyPath() string { 22 | return fmt.Sprintf("%s.enc", spec.KeyPath()) 23 | } 24 | 25 | func (spec KeyPairSpec) KeyPath() string { 26 | return filepath.Join("credentials", fmt.Sprintf("%s-key.pem", spec.Name)) 27 | } 28 | 29 | func (spec KeyPairSpec) CertPath() string { 30 | return filepath.Join("credentials", fmt.Sprintf("%s.pem", spec.Name)) 31 | } 32 | 33 | func (spec KeyPairSpec) SignerCertPath() string { 34 | return filepath.Join("credentials", fmt.Sprintf("%s.pem", spec.Signer)) 35 | } 36 | 37 | func (spec KeyPairSpec) SignerKeyPath() string { 38 | return filepath.Join("credentials", fmt.Sprintf("%s-key.pem", spec.Signer)) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/api/kubernetes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Kubernetes struct { 4 | Authentication KubernetesAuthentication `yaml:"authentication"` 5 | EncryptionAtRest EncryptionAtRest `yaml:"encryptionAtRest"` 6 | PodAutoscalerUseRestClient PodAutoscalerUseRestClient `yaml:"podAutoscalerUseRestClient"` 7 | Networking Networking `yaml:"networking,omitempty"` 8 | ControllerManager ControllerManager `yaml:"controllerManager,omitempty"` 9 | KubeScheduler KubeScheduler `yaml:"kubeScheduler,omitempty"` 10 | KubeProxy KubeProxy `yaml:"kubeProxy,omitempty"` 11 | KubeApiServer KubeApiServer `yaml:"apiServer,omitempty"` 12 | Kubelet Kubelet `yaml:"kubelet,omitempty"` 13 | APIServer KubernetesAPIServer `yaml:"apiserver,omitempty"` 14 | 15 | // Manifests is a list of manifests to be installed to the cluster. 16 | // Note that the list is sorted by their names by kube-aws so that it won't result in unnecessarily node replacements. 17 | Manifests KubernetesManifests `yaml:"manifests,omitempty"` 18 | } 19 | 20 | type KubeApiServer struct { 21 | ComputeResources ComputeResources `yaml:"resources,omitempty"` 22 | TargetRamMb int `yaml:"targetRamMb,omitempty"` 23 | } 24 | 25 | type ControllerManager struct { 26 | ComputeResources ComputeResources `yaml:"resources,omitempty"` 27 | Flags CommandLineFlags `yaml:"flags,omitempty"` 28 | } 29 | 30 | type KubeScheduler struct { 31 | ComputeResources ComputeResources `yaml:"resources,omitempty"` 32 | Flags CommandLineFlags `yaml:"flags,omitempty"` 33 | } 34 | 35 | type ComputeResources struct { 36 | Requests ResourceQuota `yaml:"requests,omitempty"` 37 | Limits ResourceQuota `yaml:"limits,omitempty"` 38 | } 39 | 40 | type ResourceQuota struct { 41 | Cpu string `yaml:"cpu"` 42 | Memory string `yaml:"memory"` 43 | } 44 | -------------------------------------------------------------------------------- /pkg/api/lauch_specification.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type LaunchSpecification struct { 8 | WeightedCapacity int `yaml:"weightedCapacity,omitempty"` 9 | InstanceType string `yaml:"instanceType,omitempty"` 10 | SpotPrice string `yaml:"spotPrice,omitempty"` 11 | RootVolume `yaml:"rootVolume,omitempty"` 12 | } 13 | 14 | func NewLaunchSpecification(weightedCapacity int, instanceType string) LaunchSpecification { 15 | return LaunchSpecification{ 16 | WeightedCapacity: weightedCapacity, 17 | InstanceType: instanceType, 18 | } 19 | } 20 | 21 | func (s *LaunchSpecification) UnmarshalYAML(unmarshal func(interface{}) error) error { 22 | type t LaunchSpecification 23 | work := t(LaunchSpecification{}) 24 | if err := unmarshal(&work); err != nil { 25 | return fmt.Errorf("failed to parse node pool config: %v", err) 26 | } 27 | *s = LaunchSpecification(work) 28 | 29 | return nil 30 | } 31 | 32 | func (c LaunchSpecification) Validate() error { 33 | if err := c.RootVolume.Validate(); err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/api/mixed_instances.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "fmt" 4 | 5 | type MixedInstances struct { 6 | Enabled bool `yaml:"enabled,omitempty"` 7 | OnDemandAllocationStrategy string `yaml:"onDemandAllocationStrategy,omitempty"` 8 | OnDemandBaseCapacity int `yaml:"onDemandBaseCapacity,omitempty"` 9 | OnDemandPercentageAboveBaseCapacity int `yaml:"onDemandPercentageAboveBaseCapacity,omitempty"` 10 | SpotAllocationStrategy string `yaml:"spotAllocationStrategy,omitempty"` 11 | SpotInstancePools int `yaml:"spotInstancePools,omitempty"` 12 | SpotMaxPrice string `yaml:"spotMaxPrice,omitempty"` 13 | InstanceTypes []string `yaml:"instanceTypes,omitempty"` 14 | } 15 | 16 | func (mi MixedInstances) Validate() error { 17 | // See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-instancesdistribution.html for valid values 18 | if mi.OnDemandAllocationStrategy != "" && mi.OnDemandAllocationStrategy != "prioritized" { 19 | return fmt.Errorf("`mixedInstances.onDemandAllocationStrategy` must be equal to 'prioritized' if specified") 20 | } 21 | if mi.OnDemandBaseCapacity < 0 { 22 | return fmt.Errorf("`mixedInstances.onDemandBaseCapacity` (%d) must be zero or greater if specified", mi.OnDemandBaseCapacity) 23 | } 24 | if mi.OnDemandPercentageAboveBaseCapacity < 0 || mi.OnDemandPercentageAboveBaseCapacity > 100 { 25 | return fmt.Errorf("`mixedInstances.onDemandPercentageAboveBaseCapacity` (%d) must be in range 0-100", mi.OnDemandPercentageAboveBaseCapacity) 26 | } 27 | if mi.SpotAllocationStrategy != "" && mi.SpotAllocationStrategy != "lowest-price" { 28 | return fmt.Errorf("`mixedInstances.spotAllocationStrategy` must be equal to 'lowest-price' if specified") 29 | } 30 | if mi.SpotInstancePools < 0 || mi.SpotInstancePools > 20 { 31 | return fmt.Errorf("`mixedInstances.spotInstancePools` (%d) must be in range 0-20", mi.SpotInstancePools) 32 | } 33 | if len(mi.SpotMaxPrice) > 255 { 34 | return fmt.Errorf("`mixedInstances.spotMaxPrice` can have a maximum length of 255") 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/api/motd_banner.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type MOTDBanner struct { 4 | Enabled bool `yaml:"enabled,omitempty"` 5 | EtcdColour ShellColour `yaml:"etcd-colour,omitempty"` 6 | KubernetesColour ShellColour `yaml:"kubernetes-colour,omitempty"` 7 | KubeAWSColour ShellColour `yaml:"kube-aws-colour,omitempty"` 8 | } 9 | 10 | func NewDefaultMOTDBanner() MOTDBanner { 11 | return MOTDBanner{ 12 | Enabled: true, 13 | EtcdColour: LightGreen, 14 | KubernetesColour: LightBlue, 15 | KubeAWSColour: LightBlue, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/api/networking.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Networking struct { 4 | AmazonVPC AmazonVPC `yaml:"amazonVPC,omitempty"` 5 | SelfHosting SelfHosting `yaml:"selfHosting,omitempty"` 6 | } 7 | 8 | type SelfHosting struct { 9 | Type string `yaml:"type"` 10 | Typha bool `yaml:"typha"` 11 | TyphaResources ComputeResources `yaml:"typhaResources,omitempty"` 12 | CalicoNodeImage Image `yaml:"calicoNodeImage"` 13 | CalicoCniImage Image `yaml:"calicoCniImage"` 14 | FlannelImage Image `yaml:"flannelImage"` 15 | FlannelCniImage Image `yaml:"flannelCniImage"` 16 | TyphaImage Image `yaml:"typhaImage"` 17 | FlannelConfig FlannelConfig `yaml:"flannelConfig"` 18 | } 19 | 20 | type FlannelConfig struct { 21 | SubnetLen int32 `yaml:"subnetLen"` 22 | } 23 | -------------------------------------------------------------------------------- /pkg/api/node_drainer.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type NodeDrainer struct { 9 | Enabled bool `yaml:"enabled"` 10 | DrainTimeout int `yaml:"drainTimeout"` 11 | IAMRole IAMRole `yaml:"iamRole,omitempty"` 12 | } 13 | 14 | func (nd *NodeDrainer) DrainTimeoutInSeconds() int { 15 | return int((time.Duration(nd.DrainTimeout) * time.Minute) / time.Second) 16 | } 17 | 18 | func (nd *NodeDrainer) Validate() error { 19 | if !nd.Enabled { 20 | return nil 21 | } 22 | 23 | if nd.DrainTimeout < 1 || nd.DrainTimeout > 60 { 24 | return fmt.Errorf("Drain timeout must be an integer between 1 and 60, but was %d", nd.DrainTimeout) 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/api/node_drainer_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDrainTimeoutInSeconds(t *testing.T) { 8 | testCases := []struct { 9 | timeoutInMins int 10 | timeoutInSecs int 11 | }{ 12 | { 13 | timeoutInMins: 0, 14 | timeoutInSecs: 0, 15 | }, 16 | { 17 | timeoutInMins: 1, 18 | timeoutInSecs: 60, 19 | }, 20 | { 21 | timeoutInMins: 2, 22 | timeoutInSecs: 120, 23 | }, 24 | } 25 | 26 | for _, testCase := range testCases { 27 | drainer := NodeDrainer{ 28 | DrainTimeout: testCase.timeoutInMins, 29 | } 30 | actual := drainer.DrainTimeoutInSeconds() 31 | if actual != testCase.timeoutInSecs { 32 | t.Errorf("Expected drain timeout in secs to be %d, but was %d", testCase.timeoutInSecs, actual) 33 | } 34 | } 35 | } 36 | 37 | func TestValidate(t *testing.T) { 38 | testCases := []struct { 39 | enabled bool 40 | drainTimeout int 41 | isValid bool 42 | }{ 43 | // Invalid, drainTimeout is < 1 44 | { 45 | enabled: true, 46 | drainTimeout: 0, 47 | isValid: false, 48 | }, 49 | 50 | // Invalid, drainTimeout > 60 51 | { 52 | enabled: true, 53 | drainTimeout: 61, 54 | isValid: false, 55 | }, 56 | 57 | // Valid, disabled 58 | { 59 | enabled: false, 60 | drainTimeout: 0, 61 | isValid: true, 62 | }, 63 | 64 | // Valid, timeout within boundaries 65 | { 66 | enabled: true, 67 | drainTimeout: 1, 68 | isValid: true, 69 | }, 70 | 71 | // Valid, timeout within boundaries 72 | { 73 | enabled: true, 74 | drainTimeout: 60, 75 | isValid: true, 76 | }, 77 | } 78 | 79 | for _, testCase := range testCases { 80 | drainer := NodeDrainer{ 81 | Enabled: testCase.enabled, 82 | DrainTimeout: testCase.drainTimeout, 83 | } 84 | 85 | err := drainer.Validate() 86 | if testCase.isValid && err != nil { 87 | t.Errorf("Expected node drainer to be valid, but it was not: %v", err) 88 | } 89 | 90 | if !testCase.isValid && err == nil { 91 | t.Errorf("Expected node drainer to be invalid, but it was not") 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/api/node_labels.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | type NodeLabels map[string]string 10 | 11 | func (l NodeLabels) Enabled() bool { 12 | return len(l) > 0 13 | } 14 | 15 | // Returns key=value pairs separated by ',' to be passed to kubelet's `--node-labels` flag 16 | func (l NodeLabels) String() string { 17 | labels := []string{} 18 | keys := []string{} 19 | for k, _ := range l { 20 | keys = append(keys, k) 21 | } 22 | sort.Strings(keys) 23 | for _, k := range keys { 24 | v := l[k] 25 | if len(v) > 0 { 26 | labels = append(labels, fmt.Sprintf("%s=%s", k, v)) 27 | } else { 28 | labels = append(labels, fmt.Sprintf("%s", k)) 29 | } 30 | } 31 | return strings.Join(labels, ",") 32 | } 33 | -------------------------------------------------------------------------------- /pkg/api/node_settings.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type NodeSettings struct { 4 | FeatureGates FeatureGates `yaml:"featureGates"` 5 | NodeLabels NodeLabels `yaml:"nodeLabels"` 6 | Taints Taints `yaml:"taints"` 7 | } 8 | 9 | func newNodeSettings() NodeSettings { 10 | return NodeSettings{ 11 | FeatureGates: FeatureGates{}, 12 | NodeLabels: NodeLabels{}, 13 | Taints: Taints{}, 14 | } 15 | } 16 | 17 | func (s NodeSettings) Validate() error { 18 | if err := s.Taints.Validate(); err != nil { 19 | return err 20 | } 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /pkg/api/oidc.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Oidc struct { 4 | Enabled bool `yaml:"enabled"` 5 | IssuerUrl string `yaml:"issuerUrl"` 6 | ClientId string `yaml:"clientId"` 7 | UsernameClaim string `yaml:"usernameClaim"` 8 | GroupsClaim string `yaml:"groupsClaim,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/api/pki.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type PKI struct { 4 | KeyPairs []KeyPairSpec `yaml:"keypairs,omitempty"` 5 | } 6 | -------------------------------------------------------------------------------- /pkg/api/plugin_config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/kubernetes-incubator/kube-aws/logger" 5 | "github.com/kubernetes-incubator/kube-aws/plugin/pluginutil" 6 | ) 7 | 8 | type PluginConfigs map[string]PluginConfig 9 | 10 | func (pcs PluginConfigs) Merge(m PluginConfigs) (PluginConfigs, error) { 11 | var err error 12 | merged := PluginConfigs{} 13 | for name, pc := range pcs { 14 | merged[name] = pc 15 | } 16 | for name, pc := range m { 17 | logger.Debugf("PluginConfigs.Merge() Plugin %s: %+v", name, pc) 18 | merged[name], err = merged[name].Merge(pc) 19 | if err != nil { 20 | return merged, err 21 | } 22 | } 23 | return merged, nil 24 | } 25 | 26 | func (pcs PluginConfigs) PluginIsEnabled(name string) bool { 27 | var pc PluginConfig 28 | var ok bool 29 | if pc, ok = pcs[name]; !ok { 30 | return false 31 | } 32 | return pc.Enabled 33 | } 34 | 35 | func (pcs PluginConfigs) PluginExists(name string) bool { 36 | _, ok := pcs[name] 37 | return ok 38 | } 39 | 40 | type PluginConfig struct { 41 | Enabled bool `yaml:"enabled,omitempty"` 42 | Values `yaml:",inline"` 43 | } 44 | 45 | func (p PluginConfig) Merge(m PluginConfig) (PluginConfig, error) { 46 | var err error 47 | result := p 48 | logger.Debugf("PluginConfig.Merge() %+v into %+v", m, p) 49 | result.Enabled = m.Enabled 50 | result.Values, err = pluginutil.MergeValues(p.Values, m.Values) 51 | logger.Debugf("PluginConfig.Merge() result %+v", result) 52 | return result, err 53 | } 54 | -------------------------------------------------------------------------------- /pkg/api/region.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Region struct { 9 | Name string `yaml:"region,omitempty"` 10 | } 11 | 12 | func RegionForName(name string) Region { 13 | return Region{ 14 | Name: name, 15 | } 16 | } 17 | 18 | func (r Region) PrivateDomainName() string { 19 | if r.Name == "us-east-1" { 20 | return "ec2.internal" 21 | } 22 | return fmt.Sprintf("%s.compute.internal", r.Name) 23 | } 24 | 25 | func (r Region) PublicComputeDomainName() string { 26 | switch r.Name { 27 | case "us-east-1": 28 | return fmt.Sprintf("compute-1.%s", r.PublicDomainName()) 29 | default: 30 | return fmt.Sprintf("%s.compute.%s", r.Name, r.PublicDomainName()) 31 | } 32 | } 33 | 34 | func (r Region) PublicDomainName() string { 35 | if r.IsChina() { 36 | return "amazonaws.com.cn" 37 | } 38 | return "amazonaws.com" 39 | } 40 | 41 | func (r Region) String() string { 42 | return r.Name 43 | } 44 | 45 | func (r Region) S3Endpoint() string { 46 | if r.IsChina() { 47 | return fmt.Sprintf("https://s3.%s.amazonaws.com.cn", r.Name) 48 | } 49 | if r.IsGovcloud() { 50 | return fmt.Sprintf("https://s3-%s.amazonaws.com", r.Name) 51 | } 52 | return "https://s3.amazonaws.com" 53 | } 54 | 55 | func (r Region) Partition() string { 56 | if r.IsChina() { 57 | return "aws-cn" 58 | } 59 | if r.IsGovcloud() { 60 | return "aws-us-gov" 61 | } 62 | return "aws" 63 | } 64 | 65 | func (r Region) IsChina() bool { 66 | return strings.HasPrefix(r.Name, "cn-") 67 | } 68 | 69 | func (r Region) IsGovcloud() bool { 70 | return strings.HasPrefix(r.Name, "us-gov-") 71 | } 72 | 73 | func (r Region) IsEmpty() bool { 74 | return r.Name == "" 75 | } 76 | 77 | func (r Region) SupportsKMS() bool { 78 | return !r.IsChina() 79 | } 80 | 81 | func (r Region) SupportsNetworkLoadBalancers() bool { 82 | return !r.IsChina() 83 | } 84 | -------------------------------------------------------------------------------- /pkg/api/root_volume.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubernetes-incubator/kube-aws/logger" 6 | ) 7 | 8 | type RootVolume struct { 9 | Size int `yaml:"size,omitempty"` 10 | Type string `yaml:"type,omitempty"` 11 | IOPS int `yaml:"iops,omitempty"` 12 | UnknownKeys `yaml:",inline"` 13 | } 14 | 15 | func NewGp2RootVolume(size int) RootVolume { 16 | return RootVolume{ 17 | Size: size, 18 | IOPS: 0, 19 | Type: "gp2", 20 | } 21 | } 22 | 23 | func NewIo1RootVolume(size int, iops int) RootVolume { 24 | return RootVolume{ 25 | Size: size, 26 | IOPS: iops, 27 | Type: "io1", 28 | } 29 | } 30 | 31 | func (v RootVolume) Validate() error { 32 | if v.Type == "io1" { 33 | if v.IOPS < 100 || v.IOPS > 20000 { 34 | return fmt.Errorf(`invalid rootVolumeIOPS %d in %+v: rootVolumeIOPS must be between 100 and 20000`, v.IOPS, v) 35 | } 36 | } else { 37 | if v.IOPS != 0 { 38 | return fmt.Errorf(`invalid rootVolumeIOPS %d for volume type "%s" in %+v": rootVolumeIOPS must be 0 when rootVolumeType is "standard" or "gp2"`, v.IOPS, v.Type, v) 39 | } 40 | 41 | if v.Type != "standard" && v.Type != "gp2" { 42 | return fmt.Errorf(`invalid rootVolumeType "%s" in %+v: rootVolumeType must be one of "standard", "gp2", "io1"`, v.Type, v) 43 | } 44 | } 45 | return nil 46 | } 47 | 48 | func (v RootVolume) RootVolumeIOPS() int { 49 | logger.Warn("RootVolumeIOPS is deprecated and will be removed in v0.9.7. Please use RootVolume.IOPS instead") 50 | return v.IOPS 51 | } 52 | 53 | func (v RootVolume) RootVolumeType() string { 54 | logger.Warn("RootVolumeType is deprecated and will be removed in v0.9.7. Please use RootVolume.Type instead") 55 | return v.Type 56 | } 57 | 58 | func (v RootVolume) RootVolumeSize() int { 59 | logger.Warn("RootVolumeSize is deprecated and will be removed in v0.9.7. Please use RootVolume.Size instead") 60 | return v.Size 61 | } 62 | -------------------------------------------------------------------------------- /pkg/api/s3_folders.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type S3Folders struct { 9 | clusterName string 10 | s3URI string 11 | } 12 | 13 | func NewS3Folders(s3URI string, clusterName string) S3Folders { 14 | return S3Folders{ 15 | s3URI: s3URI, 16 | clusterName: clusterName, 17 | } 18 | } 19 | 20 | func (n S3Folders) root() S3Folder { 21 | return newS3Folder(n.s3URI) 22 | } 23 | 24 | func (n S3Folders) Cluster() S3Folder { 25 | return n.root().subFolder(fmt.Sprintf("kube-aws/clusters/%s", n.clusterName)) 26 | } 27 | 28 | func (n S3Folders) ClusterBackups() S3Folder { 29 | return n.Cluster().subFolder("backup") 30 | } 31 | 32 | func (n S3Folders) ClusterExportedStacks() S3Folder { 33 | return n.Cluster().subFolder("exported/stacks") 34 | } 35 | 36 | type S3Folder struct { 37 | s3URI string 38 | } 39 | 40 | func newS3Folder(uri string) S3Folder { 41 | return S3Folder{ 42 | s3URI: strings.TrimSuffix(uri, "/"), 43 | } 44 | } 45 | 46 | func (f S3Folder) Path() string { 47 | uri := strings.TrimSuffix(f.s3URI, "/") 48 | return strings.TrimPrefix(uri, "s3://") 49 | } 50 | 51 | func (f S3Folder) URI() string { 52 | return f.s3URI 53 | } 54 | 55 | func (f S3Folder) subFolder(name string) S3Folder { 56 | return newS3Folder(fmt.Sprintf("%s/%s", f.s3URI, name)) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/api/security_group.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // SecurityGroup references one of existing security groups in your AWS account 4 | type SecurityGroup struct { 5 | Identifier `yaml:",inline"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/api/shell_colour.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "fmt" 4 | 5 | type ShellColour int 6 | 7 | const ( 8 | DefaultColour ShellColour = iota 9 | Black 10 | Red 11 | Green 12 | Yellow 13 | Blue 14 | Magenta 15 | Cyan 16 | White 17 | DarkGray 18 | LightRed 19 | LightGreen 20 | LightYellow 21 | LightBlue 22 | LightMagenta 23 | LightCyan 24 | LightWhite 25 | ) 26 | 27 | var ShellColourCodeMap map[ShellColour]string = map[ShellColour]string{ 28 | DefaultColour: `0m`, 29 | Black: `0;30m`, 30 | Red: `0;31m`, 31 | Green: `0;32m`, 32 | Yellow: `0;33m`, 33 | Blue: `0;34m`, 34 | Magenta: `0;35m`, 35 | Cyan: `0;36m`, 36 | White: `0;37m`, 37 | DarkGray: `1;90m`, 38 | LightRed: `1;31m`, 39 | LightGreen: `1;32m`, 40 | LightYellow: `1;33m`, 41 | LightBlue: `1;34m`, 42 | LightMagenta: `1;35m`, 43 | LightCyan: `1;36m`, 44 | LightWhite: `1;37m`, 45 | } 46 | 47 | func (colour ShellColour) PCOn() string { 48 | return fmt.Sprintf("\\[%s\\]", colour.On()) 49 | } 50 | 51 | func (colour ShellColour) PCOff() string { 52 | return fmt.Sprintf("\\[%s\\]", colour.Off()) 53 | } 54 | 55 | func (colour ShellColour) On() string { 56 | if colour.IsAShellColour() { 57 | return fmt.Sprintf("\\033[%s", ShellColourCodeMap[colour]) 58 | } else { 59 | return fmt.Sprintf("\\033[%s", ShellColourCodeMap[DefaultColour]) 60 | } 61 | } 62 | 63 | func (colour ShellColour) Off() string { 64 | return fmt.Sprintf("\\033[%s", ShellColourCodeMap[DefaultColour]) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/api/stack_name_overrides.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type StackNameOverrides struct { 4 | ControlPlane string `yaml:"controlPlane,omitempty"` 5 | Network string `yaml:"network,omitempty"` 6 | Etcd string `yaml:"etcd,omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /pkg/api/subnet_reference.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // SubnetReference references one of subnets defined in the top-level of cluster.yaml 4 | type SubnetReference struct { 5 | // Name is the unique name of subnet to be referenced. 6 | // The subnet referenced by this name should be defined in the `subnets[]` field in the top-level of cluster.yaml 7 | Name string `yaml:"name,omitempty"` 8 | } 9 | -------------------------------------------------------------------------------- /pkg/api/subnets.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kubernetes-incubator/kube-aws/logger" 7 | ) 8 | 9 | type Subnets []Subnet 10 | 11 | func (s Subnets) ContainsBothPrivateAndPublic() bool { 12 | allPublic := true 13 | allPrivate := true 14 | for _, subnet := range s { 15 | allPublic = allPublic && subnet.Public() 16 | allPrivate = allPrivate && subnet.Private 17 | } 18 | return !allPublic && !allPrivate 19 | } 20 | 21 | func (ss Subnets) ImportFromNetworkStack() (Subnets, error) { 22 | result := make(Subnets, len(ss)) 23 | // Import all the managed subnets from the main cluster i.e. don't create subnets inside the node pool cfn stack 24 | for i, s := range ss { 25 | if !s.HasIdentifier() { 26 | logger.Debugf("Subnet %s does not have an identifier, importing from NetworkStack...", s.Name) 27 | logicalName, err := s.LogicalNameOrErr() 28 | if err != nil { 29 | return result, err 30 | } 31 | stackOutputName := fmt.Sprintf(`{"Fn::ImportValue":{"Fn::Sub":"${NetworkStackName}-%s"}}`, logicalName) 32 | az := s.AvailabilityZone 33 | if s.Private { 34 | result[i] = NewPrivateSubnetFromFn(az, stackOutputName) 35 | } else { 36 | result[i] = NewPublicSubnetFromFn(az, stackOutputName) 37 | } 38 | } else { 39 | logger.Debugf("Subnet %s has an identifier, using that: %+v", s.Name, s) 40 | result[i] = s 41 | } 42 | } 43 | return result, nil 44 | } 45 | 46 | func (ss Subnets) ImportFromNetworkStackRetainingNames() (Subnets, error) { 47 | result, err := ss.ImportFromNetworkStack() 48 | if err != nil { 49 | return result, err 50 | } 51 | for i, s := range ss { 52 | logger.Debugf("ImportFromNetworkStackRetainingNames restoring name to %s", s.Name) 53 | result[i].Name = s.Name 54 | } 55 | return result, nil 56 | } 57 | 58 | func (ss Subnets) RefByName(name string) (string, error) { 59 | for _, subnet := range ss { 60 | if subnet.Name == name { 61 | return subnet.Ref(), nil 62 | } 63 | } 64 | return "", fmt.Errorf("No subnets found with name: %s", name) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/api/subnets_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSubnetsMixed(t *testing.T) { 8 | 9 | public := Subnet{Name: "Public", AvailabilityZone: "ap-northeast-1a", InstanceCIDR: "10.0.0.0/24", Private: false} 10 | s1 := Subnets{ 11 | public, 12 | } 13 | if s1.ContainsBothPrivateAndPublic() { 14 | t.Error("Func ContainsBothPrivateAndPublic should return false when there is only one public subnet in Subnets but it did not") 15 | } 16 | 17 | private := Subnet{Name: "Private", AvailabilityZone: "ap-northeast-1b", InstanceCIDR: "10.0.1.0/24", Private: true} 18 | s2 := Subnets{ 19 | private, 20 | } 21 | if s2.ContainsBothPrivateAndPublic() { 22 | t.Error("Func ContainsBothPrivateAndPublic should return false when there is only one private subnet in Subnets but it did not") 23 | } 24 | 25 | s3 := Subnets{ 26 | public, 27 | private, 28 | } 29 | if !s3.ContainsBothPrivateAndPublic() { 30 | t.Error("Func ContainsBothPrivateAndPublic should return true when the set of subnets contains both private and public subnet(s) but it did not") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/api/taint.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Taints is a list of taints 9 | type Taints []Taint 10 | 11 | // String returns a comma-separated list of taints 12 | func (t Taints) String() string { 13 | ts := []string{} 14 | for _, t := range t { 15 | ts = append(ts, t.String()) 16 | } 17 | return strings.Join(ts, ",") 18 | } 19 | 20 | // Validate returns an error if the list of taints are invalid as a group 21 | func (t Taints) Validate() error { 22 | keyEffects := map[string]int{} 23 | 24 | for _, taint := range t { 25 | if err := taint.Validate(); err != nil { 26 | return err 27 | } 28 | 29 | keyEffect := taint.Key + ":" + taint.Effect 30 | if _, ok := keyEffects[keyEffect]; ok { 31 | return fmt.Errorf("taints must be unique by key and effect pair") 32 | } else { 33 | keyEffects[keyEffect] = 1 34 | } 35 | } 36 | 37 | return nil 38 | } 39 | 40 | // Taint is a k8s node taint which is added to nodes which requires pods to tolerate 41 | type Taint struct { 42 | Key string `yaml:"key"` 43 | Value string `yaml:"value"` 44 | Effect string `yaml:"effect"` 45 | } 46 | 47 | // String returns a taint represented in string 48 | func (t Taint) String() string { 49 | return fmt.Sprintf("%s=%s:%s", t.Key, t.Value, t.Effect) 50 | } 51 | 52 | // Validate returns an error if the taint is invalid 53 | func (t Taint) Validate() error { 54 | if len(t.Key) == 0 { 55 | return fmt.Errorf("expected taint key to be a non-empty string") 56 | } 57 | 58 | if t.Effect != "NoSchedule" && t.Effect != "PreferNoSchedule" && t.Effect != "NoExecute" { 59 | return fmt.Errorf("invalid taint effect: %s", t.Effect) 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/api/unknown_keys.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | type UnknownKeys map[string]interface{} 10 | 11 | func (unknownKeys UnknownKeys) FailWhenUnknownKeysFound(keyPath string) error { 12 | if unknownKeys != nil && len(unknownKeys) > 0 { 13 | ks := []string{} 14 | for k, _ := range unknownKeys { 15 | ks = append(ks, k) 16 | } 17 | 18 | sort.Strings(ks) 19 | 20 | if keyPath != "" { 21 | return fmt.Errorf("unknown keys found in %s: %s", keyPath, strings.Join(ks, ", ")) 22 | } 23 | return fmt.Errorf("unknown keys found: %s", strings.Join(ks, ", ")) 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/api/unknown_keys_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/go-yaml/yaml" 9 | ) 10 | 11 | type fakeConfig struct { 12 | Name string `yaml:"name,omitempty"` 13 | UnknownKeys `yaml:",inline"` 14 | } 15 | 16 | func TestUnknownKeys(t *testing.T) { 17 | t.Run("WithoutKeyPath", func(t *testing.T) { 18 | data := `name: myname 19 | unknownKey1: unusedValue1 20 | unknownKey2: unusedValue2 21 | ` 22 | c := fakeConfig{} 23 | yamlErr := yaml.Unmarshal([]byte(data), &c) 24 | if yamlErr != nil { 25 | t.Errorf("bug in test! %v", yamlErr) 26 | t.FailNow() 27 | } 28 | e := c.FailWhenUnknownKeysFound("") 29 | if e == nil { 30 | t.Error("expected to fail but succeeded") 31 | } 32 | m := fmt.Sprintf("%v", e) 33 | if !strings.Contains(m, `unknown keys found: unknownKey1, unknownKey2`) { 34 | t.Errorf("unexpected error message from FailWhenUnknownKeysFound(): %v", m) 35 | } 36 | }) 37 | 38 | t.Run("WithKeyPath", func(t *testing.T) { 39 | data := `name: myname 40 | unknownKey1: unusedValue1 41 | unknownKey2: unusedValue2 42 | ` 43 | c := fakeConfig{} 44 | yamlErr := yaml.Unmarshal([]byte(data), &c) 45 | if yamlErr != nil { 46 | t.Errorf("bug in test! %v", yamlErr) 47 | t.FailNow() 48 | } 49 | e := c.FailWhenUnknownKeysFound("worker.nodePools[0]") 50 | if e == nil { 51 | t.Error("expected to fail but succeeded") 52 | } 53 | m := fmt.Sprintf("%v", e) 54 | if !strings.Contains(m, `unknown keys found in worker.nodePools[0]: unknownKey1, unknownKey2`) { 55 | t.Errorf("unexpected error message from FailWhenUnknownKeysFound(): %v", m) 56 | } 57 | }) 58 | 59 | t.Run("Empty", func(t *testing.T) { 60 | data := `name: myname 61 | ` 62 | c := fakeConfig{} 63 | yamlErr := yaml.Unmarshal([]byte(data), &c) 64 | if yamlErr != nil { 65 | t.Errorf("bug in test! %v", yamlErr) 66 | t.FailNow() 67 | } 68 | e := c.FailWhenUnknownKeysFound("") 69 | if e != nil { 70 | t.Errorf("expected to succeed but failed: %v", e) 71 | } 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/api/vpc.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // kube-aws manages at most one VPC per cluster 4 | // If ID or IDFromStackOutput is non-zero, kube-aws doesn't manage the VPC but its users' responsibility to 5 | // provide properly configured one to be reused by kube-aws. 6 | // More concretely: 7 | // * If an user is going to reuse an existing VPC, it must have an internet gateway attached and 8 | // * A valid internet gateway ID must be provided via `internetGateway.id` or `internetGateway.idFromStackOutput`. 9 | // In other words, kube-aws doesn't create an internet gateway in an existing VPC. 10 | type VPC struct { 11 | Identifier `yaml:",inline"` 12 | } 13 | 14 | func (v VPC) ImportFromNetworkStack() VPC { 15 | if !v.HasIdentifier() { 16 | // Otherwise import the VPC ID from the control-plane stack 17 | v.IDFromFn = `{"Fn::ImportValue":{"Fn::Sub":"${NetworkStackName}-VPC"}}` 18 | } 19 | return v 20 | } 21 | -------------------------------------------------------------------------------- /pkg/model/api_endpoint.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/kubernetes-incubator/kube-aws/pkg/api" 4 | 5 | // APIEndpoint represents a Kubernetes API endpoint 6 | type APIEndpoint struct { 7 | // APIEndpoint derives the user-provided configuration in an item of an `apiEndpoints` array and adds various computed settings 8 | api.APIEndpoint 9 | // LoadBalancer is the load balancer serving this API endpoint if any 10 | LoadBalancer APIEndpointLB 11 | } 12 | -------------------------------------------------------------------------------- /pkg/model/const.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | const ( 4 | credentialsDir = "credentials" 5 | userDataDir = "userdata" 6 | 7 | networkStackName = "network" 8 | etcdStackName = "etcd" 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/model/credentials.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | "github.com/kubernetes-incubator/kube-aws/credential" 6 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 7 | ) 8 | 9 | func LoadCredentials(sess *session.Session, cfg *Config, opts api.StackTemplateOptions) (*credential.CompactAssets, error) { 10 | s := &Context{Session: sess} 11 | return s.LoadCredentials(cfg, opts) 12 | } 13 | 14 | func (s *Context) LoadCredentials(cfg *Config, opts api.StackTemplateOptions) (*credential.CompactAssets, error) { 15 | if cfg.AssetsEncryptionEnabled() { 16 | kmsConfig := credential.NewKMSConfig(cfg.KMSKeyARN, s.ProvidedEncryptService, s.Session) 17 | compactAssets, err := credential.ReadOrCreateCompactAssets(opts.AssetsDir, cfg.ManageCertificates, true, kmsConfig) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return compactAssets, nil 23 | } else { 24 | rawAssets, err := credential.ReadOrCreateUnencryptedCompactAssets(opts.AssetsDir, cfg.ManageCertificates, true) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return rawAssets, nil 30 | } 31 | } 32 | 33 | func NewCredentialGenerator(c *Config) *credential.Generator { 34 | r := &credential.Generator{ 35 | TLSCADurationDays: c.TLSCADurationDays, 36 | TLSCertDurationDays: c.TLSCertDurationDays, 37 | ManageCertificates: c.ManageCertificates, 38 | Region: c.Region.String(), 39 | APIServerExternalDNSNames: c.ExternalDNSNames(), 40 | APIServerAdditionalDNSSans: c.CustomApiServerSettings.AdditionalDnsSANs, 41 | APIServerAdditionalIPAddressSans: c.CustomApiServerSettings.AdditionalIPAddresses, 42 | EtcdNodeDNSNames: c.EtcdCluster().DNSNames(), 43 | ServiceCIDR: c.ServiceCIDR, 44 | } 45 | 46 | return r 47 | } 48 | 49 | func GenerateAssetsOnDisk(sess *session.Session, c *Config, dir string, opts credential.GeneratorOptions) (*credential.RawAssetsOnDisk, error) { 50 | s := &Context{Session: sess} 51 | return s.GenerateAssetsOnDisk(c, dir, opts) 52 | } 53 | 54 | func (s *Context) GenerateAssetsOnDisk(c *Config, dir string, opts credential.GeneratorOptions) (*credential.RawAssetsOnDisk, error) { 55 | r := NewCredentialGenerator(c) 56 | return r.GenerateAssetsOnDisk(dir, opts) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/model/etcd_cluster.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 8 | ) 9 | 10 | type EtcdCluster struct { 11 | api.EtcdCluster 12 | Network 13 | region api.Region 14 | nodeCount int 15 | } 16 | 17 | func NewEtcdCluster(config api.EtcdCluster, region api.Region, network Network, nodeCount int) EtcdCluster { 18 | return EtcdCluster{ 19 | EtcdCluster: config, 20 | region: region, 21 | Network: network, 22 | nodeCount: nodeCount, 23 | } 24 | } 25 | 26 | func (c EtcdCluster) Region() api.Region { 27 | return c.region 28 | } 29 | 30 | func (c EtcdCluster) NodeCount() int { 31 | return c.nodeCount 32 | } 33 | 34 | func (c EtcdCluster) DNSNames() []string { 35 | var dnsName string 36 | if c.GetMemberIdentityProvider() == api.MemberIdentityProviderEIP { 37 | // Used when `etcd.memberIdentityProvider` is set to "eip" 38 | dnsName = fmt.Sprintf("*.%s", c.region.PublicComputeDomainName()) 39 | } 40 | if c.GetMemberIdentityProvider() == api.MemberIdentityProviderENI { 41 | if c.InternalDomainName != "" { 42 | // Used when `etcd.memberIdentityProvider` is set to "eni" with non-empty `etcd.internalDomainName` 43 | dnsName = fmt.Sprintf("*.%s", c.InternalDomainName) 44 | } else { 45 | dnsName = fmt.Sprintf("*.%s", c.region.PrivateDomainName()) 46 | } 47 | } 48 | return []string{dnsName} 49 | } 50 | 51 | func (c EtcdCluster) LogicalName() string { 52 | d := regexp.MustCompile(`\.`) 53 | return fmt.Sprintf("Etcd%s", d.ReplaceAllString(c.EtcdCluster.MajorMinorVersion(), `dot`)) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/model/etcd_nodes.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 6 | ) 7 | 8 | // NewEtcdNodes derives etcd nodes from user-provided etcd node configs 9 | func NewEtcdNodes(nodeConfigs []api.EtcdNode, cluster EtcdCluster) ([]EtcdNode, error) { 10 | count := cluster.NodeCount() 11 | 12 | result := make([]EtcdNode, count) 13 | for etcdIndex := 0; etcdIndex < count; etcdIndex++ { 14 | 15 | //Round-robin etcd instances across all available subnets 16 | subnetIndex := etcdIndex % len(cluster.Subnets()) 17 | subnet := cluster.Subnets()[subnetIndex] 18 | 19 | nodeConfig := api.EtcdNode{} 20 | if len(nodeConfigs) == count { 21 | nodeConfig = nodeConfigs[etcdIndex] 22 | } 23 | 24 | if subnet.ManageNATGateway() { 25 | ngw, err := cluster.NATGatewayForSubnet(subnet) 26 | 27 | if err != nil { 28 | return nil, fmt.Errorf("failed to determine nat gateway for subnet %s: %v", subnet.LogicalName(), err) 29 | } 30 | 31 | result[etcdIndex] = NewEtcdNodeDependsOnManagedNGW(cluster, etcdIndex, nodeConfig, subnet, *ngw) 32 | } else { 33 | result[etcdIndex] = NewEtcdNode(cluster, etcdIndex, nodeConfig, subnet) 34 | } 35 | 36 | //http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html#concepts-private-addresses 37 | 38 | } 39 | return result, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/model/info.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "text/tabwriter" 8 | ) 9 | 10 | type Info struct { 11 | Name string 12 | ControllerHosts []string 13 | } 14 | 15 | func (c *Info) String() string { 16 | buf := new(bytes.Buffer) 17 | w := new(tabwriter.Writer) 18 | w.Init(buf, 0, 8, 0, '\t', 0) 19 | 20 | fmt.Fprintf(w, "Cluster Name:\t%s\n", c.Name) 21 | fmt.Fprintf(w, "Controller DNS Names:\t%s\n", strings.Join(c.ControllerHosts, ", ")) 22 | 23 | w.Flush() 24 | return buf.String() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/model/init.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-yaml/yaml" 6 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 7 | "io/ioutil" 8 | ) 9 | 10 | func ClusterFromFile(filename string) (*api.Cluster, error) { 11 | data, err := ioutil.ReadFile(filename) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | c, err := ClusterFromBytes(data) 17 | if err != nil { 18 | return nil, fmt.Errorf("file %s: %v", filename, err) 19 | } 20 | 21 | return c, nil 22 | } 23 | 24 | // ClusterFromBytes Necessary for unit tests, which store configs as hardcoded strings 25 | func ClusterFromBytes(data []byte) (*api.Cluster, error) { 26 | c := api.NewDefaultCluster() 27 | 28 | if err := yaml.Unmarshal(data, c); err != nil { 29 | return c, fmt.Errorf("failed to parse cluster: %v", err) 30 | } 31 | 32 | c.HyperkubeImage.Tag = c.K8sVer 33 | 34 | if err := c.Load(); err != nil { 35 | return c, err 36 | } 37 | 38 | return c, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/model/network.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 6 | ) 7 | 8 | type Network interface { 9 | Subnets() []api.Subnet 10 | NATGateways() []api.NATGateway 11 | NATGatewayForSubnet(api.Subnet) (*api.NATGateway, error) 12 | } 13 | 14 | type networkImpl struct { 15 | subnets []api.Subnet 16 | natGateways []api.NATGateway 17 | } 18 | 19 | func NewNetwork(subnets []api.Subnet, natGateways []api.NATGateway) Network { 20 | return networkImpl{ 21 | subnets: subnets, 22 | natGateways: natGateways, 23 | } 24 | } 25 | 26 | func (n networkImpl) Subnets() []api.Subnet { 27 | return n.subnets 28 | } 29 | 30 | func (n networkImpl) NATGateways() []api.NATGateway { 31 | return n.natGateways 32 | } 33 | 34 | func (n networkImpl) NATGatewayForSubnet(s api.Subnet) (*api.NATGateway, error) { 35 | for _, ngw := range n.NATGateways() { 36 | if ngw.IsConnectedToPrivateSubnet(s) { 37 | return &ngw, nil 38 | } 39 | } 40 | return nil, fmt.Errorf(`subnet "%s" doesn't have a corresponding nat gateway in: %v`, s.LogicalName(), n.natGateways) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/model/node_pool_deployment_settings.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 6 | ) 7 | 8 | type NodePoolDeploymentSettings struct { 9 | api.WorkerNodePool 10 | api.Experimental 11 | api.DeploymentSettings 12 | } 13 | 14 | func (c NodePoolDeploymentSettings) WorkerSecurityGroupRefs() []string { 15 | refs := []string{} 16 | 17 | if c.Experimental.LoadBalancer.Enabled { 18 | for _, sgId := range c.Experimental.LoadBalancer.SecurityGroupIds { 19 | refs = append(refs, fmt.Sprintf(`"%s"`, sgId)) 20 | } 21 | } 22 | 23 | if c.Experimental.TargetGroup.Enabled { 24 | for _, sgId := range c.Experimental.TargetGroup.SecurityGroupIds { 25 | refs = append(refs, fmt.Sprintf(`"%s"`, sgId)) 26 | } 27 | } 28 | 29 | for _, sgId := range c.SecurityGroupIds { 30 | refs = append(refs, fmt.Sprintf(`"%s"`, sgId)) 31 | } 32 | 33 | return refs 34 | } 35 | 36 | func (c NodePoolDeploymentSettings) StackTags() map[string]string { 37 | tags := map[string]string{} 38 | 39 | for k, v := range c.DeploymentSettings.StackTags { 40 | tags[k] = v 41 | } 42 | 43 | return tags 44 | } 45 | 46 | func (c NodePoolDeploymentSettings) Validate() error { 47 | sgRefs := c.WorkerSecurityGroupRefs() 48 | numSGs := len(sgRefs) 49 | 50 | if numSGs > 4 { 51 | return fmt.Errorf("number of user provided security groups must be less than or equal to 4 but was %d (actual EC2 limit is 5 but one of them is reserved for kube-aws) : %v", numSGs, sgRefs) 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/model/node_pool_stack_info.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/tabwriter" 7 | ) 8 | 9 | type NodePoolStackInfo struct { 10 | Name string 11 | } 12 | 13 | func (c *NodePoolStackInfo) String() string { 14 | buf := new(bytes.Buffer) 15 | w := new(tabwriter.Writer) 16 | w.Init(buf, 0, 8, 0, '\t', 0) 17 | 18 | fmt.Fprintf(w, "Cluster Name:\t%s\n", c.Name) 19 | 20 | w.Flush() 21 | return buf.String() 22 | } 23 | -------------------------------------------------------------------------------- /pki/ca.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "time" 7 | ) 8 | 9 | func NewCA(caDurationDays int, CommonName string) (*rsa.PrivateKey, *x509.Certificate, error) { 10 | caKey, err := NewPrivateKey() 11 | if err != nil { 12 | return nil, nil, err 13 | } 14 | 15 | // Convert from days to time.Duration 16 | caDuration := time.Duration(caDurationDays) * 24 * time.Hour 17 | 18 | caConfig := CACertConfig{ 19 | CommonName: CommonName, 20 | Organization: "kube-aws", 21 | Duration: caDuration, 22 | } 23 | caCert, err := NewSelfSignedCACertificate(caConfig, caKey) 24 | if err != nil { 25 | return nil, nil, err 26 | } 27 | 28 | return caKey, caCert, nil 29 | } 30 | -------------------------------------------------------------------------------- /pki/keypair.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "fmt" 7 | ) 8 | 9 | func KeyPairFromPEMs(id string, certpem []byte, keypem []byte) (*KeyPair, error) { 10 | var cert *x509.Certificate 11 | var key *rsa.PrivateKey 12 | var err error 13 | if cert, err = DecodeCertificatePEM(certpem); err != nil { 14 | return nil, fmt.Errorf("failed to decode certificate pem: %v", err) 15 | } 16 | if key, err = DecodePrivateKeyPEM(keypem); err != nil { 17 | return nil, fmt.Errorf("failed to decode private key pem: %v", err) 18 | } 19 | kp := KeyPair{ 20 | Key: key, 21 | Cert: cert, 22 | id: id, 23 | keyPem: keypem, 24 | certPem: certpem, 25 | } 26 | return &kp, nil 27 | } 28 | 29 | func (keypair *KeyPair) KeyInPEM() []byte { 30 | if keypair.keyPem == nil { 31 | keypair.keyPem = EncodePrivateKeyPEM(keypair.Key) 32 | } 33 | return keypair.keyPem 34 | } 35 | 36 | func (keypair *KeyPair) CertInPEM() []byte { 37 | if keypair.certPem == nil { 38 | keypair.certPem = EncodeCertificatePEM(keypair.Cert) 39 | } 40 | return keypair.certPem 41 | } 42 | -------------------------------------------------------------------------------- /pki/rsa.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | ) 7 | 8 | const ( 9 | RSAKeySize = 2048 10 | ) 11 | 12 | func NewPrivateKey() (*rsa.PrivateKey, error) { 13 | return rsa.GenerateKey(rand.Reader, RSAKeySize) 14 | } 15 | -------------------------------------------------------------------------------- /pki/types.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | ) 7 | 8 | // KeyPair is the TLS public certificate PEM file and its associated private key PEM file that is 9 | // used by kube-aws and its plugins 10 | type KeyPair struct { 11 | Key *rsa.PrivateKey 12 | Cert *x509.Certificate 13 | 14 | id string 15 | 16 | keyPem []byte 17 | certPem []byte 18 | } 19 | -------------------------------------------------------------------------------- /plugin/loader.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "path/filepath" 7 | 8 | "github.com/go-yaml/yaml" 9 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 10 | ) 11 | 12 | type Loader struct { 13 | } 14 | 15 | func NewLoader() *Loader { 16 | return &Loader{} 17 | } 18 | 19 | func (l Loader) Load() ([]*api.Plugin, error) { 20 | plugins := []*api.Plugin{} 21 | fileInfos, _ := ioutil.ReadDir("plugins/") 22 | for _, f := range fileInfos { 23 | if f.IsDir() { 24 | p, err := l.TryToLoadPluginFromDir(filepath.Join("plugins", f.Name())) 25 | if err != nil { 26 | return []*api.Plugin{}, fmt.Errorf("Failed to load plugin from the directory %s: %v", f.Name(), err) 27 | } 28 | plugins = append(plugins, p) 29 | //fmt.Fprintf(os.Stderr, "loaded plugin %v\n", p) 30 | } 31 | } 32 | return plugins, nil 33 | } 34 | 35 | func (l Loader) TryToLoadPluginFromDir(path string) (*api.Plugin, error) { 36 | p, err := PluginFromFile(filepath.Join(path, "plugin.yaml")) 37 | if err != nil { 38 | return nil, fmt.Errorf("Failed to load plugin from %s: %v", path, err) 39 | } 40 | return p, nil 41 | } 42 | 43 | func PluginFromFile(path string) (*api.Plugin, error) { 44 | data, err := ioutil.ReadFile(path) 45 | if err != nil { 46 | return nil, fmt.Errorf("Failed to read file %s: %v", path, err) 47 | } 48 | 49 | c, err := PluginFromBytes(data) 50 | if err != nil { 51 | return nil, fmt.Errorf("Failed while processing file %s: %v", path, err) 52 | } 53 | 54 | return c, nil 55 | } 56 | 57 | func PluginFromBytes(data []byte) (*api.Plugin, error) { 58 | p := &api.Plugin{} 59 | if err := yaml.UnmarshalStrict(data, p); err != nil { 60 | return nil, fmt.Errorf("Failed to parse as yaml: %v", err) 61 | } 62 | if err := p.Validate(); err != nil { 63 | return nil, fmt.Errorf("Failed to validate plugin \"%s\": %v", p.Name, err) 64 | } 65 | return p, nil 66 | } 67 | 68 | func LoadAll() ([]*api.Plugin, error) { 69 | loaders := []*Loader{ 70 | NewLoader(), 71 | } 72 | 73 | plugins := []*api.Plugin{} 74 | for _, l := range loaders { 75 | ps, err := l.Load() 76 | if err != nil { 77 | return plugins, fmt.Errorf("Failed to load plugins: %v", err) 78 | } 79 | plugins = append(plugins, ps...) 80 | } 81 | return plugins, nil 82 | } 83 | -------------------------------------------------------------------------------- /plugin/plugincontents/loader.go: -------------------------------------------------------------------------------- 1 | package plugincontents 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "fmt" 7 | 8 | "github.com/kubernetes-incubator/kube-aws/logger" 9 | "github.com/kubernetes-incubator/kube-aws/pkg/api" 10 | "github.com/kubernetes-incubator/kube-aws/provisioner" 11 | ) 12 | 13 | type PluginFileLoader struct { 14 | p *api.Plugin 15 | 16 | FileLoader *provisioner.RemoteFileLoader 17 | } 18 | 19 | func NewPluginFileLoader(p *api.Plugin) *PluginFileLoader { 20 | return &PluginFileLoader{ 21 | p: p, 22 | } 23 | } 24 | 25 | func (l *PluginFileLoader) String(f provisioner.RemoteFileSpec) (string, error) { 26 | if f.Source.Path != "" { 27 | f.Source.Path = filepath.Join("plugins", l.p.Name, f.Source.Path) 28 | } 29 | 30 | logger.Debugf("PluginFileLoader.String(): Calling load on FileLoader with RemoteFileSpec: %+v", f) 31 | loaded, err := l.FileLoader.Load(f) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | res := loaded.Content.String() 37 | logger.Debugf("PluginFileLoader.String(): resultant string is: %+v", res) 38 | 39 | if f.Source.Path != "" && len(res) == 0 { 40 | return "", fmt.Errorf("[bug] empty file loaded from %s", f.Source.Path) 41 | } 42 | 43 | return res, nil 44 | } 45 | -------------------------------------------------------------------------------- /plugin/pluginutil/merge_values.go: -------------------------------------------------------------------------------- 1 | package pluginutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/imdario/mergo" 7 | ) 8 | 9 | func MergeValues(v, o map[string]interface{}) (map[string]interface{}, error) { 10 | work := make(map[string]interface{}) 11 | err := mergo.Merge(&work, o) 12 | if err != nil { 13 | return work, fmt.Errorf("Could not merge source plugin values into work variable") 14 | } 15 | err = mergo.Merge(&work, v) 16 | if err != nil { 17 | return work, fmt.Errorf("Could not merge target values into plugin source values") 18 | } 19 | return work, nil 20 | } 21 | -------------------------------------------------------------------------------- /provisioner/content.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "github.com/kubernetes-incubator/kube-aws/gzipcompressor" 7 | ) 8 | 9 | func (c Content) String() string { 10 | if len(c.str) == 0 && len(c.bytes) > 0 { 11 | return string(c.bytes) 12 | } 13 | return c.str 14 | } 15 | 16 | func (c *Content) UnmarshalYAML(unmarshal func(interface{}) error) error { 17 | var tmp string 18 | if err := unmarshal(&tmp); err != nil { 19 | return err 20 | } 21 | *c = NewStringContent(tmp) 22 | return nil 23 | } 24 | 25 | func (c Content) MarshalYAML() (interface{}, error) { 26 | return c.String(), nil 27 | } 28 | 29 | func NewStringContent(str string) Content { 30 | return Content{ 31 | bytes: []byte(str), 32 | str: str, 33 | } 34 | } 35 | 36 | func NewBinaryContent(bytes []byte) Content { 37 | return Content{ 38 | bytes: bytes, 39 | } 40 | } 41 | 42 | func (c Content) ToBase64() Content { 43 | bytes := []byte(base64.StdEncoding.EncodeToString(c.bytes)) 44 | return Content{ 45 | bytes: bytes, 46 | } 47 | } 48 | 49 | func (c Content) ToGzip() Content { 50 | bytes, err := gzipcompressor.BytesToGzippedBytes(c.bytes) 51 | if err != nil { 52 | panic(fmt.Errorf("Unexpected error in ToGzip: %v", err)) 53 | } 54 | return Content{ 55 | bytes: bytes, 56 | } 57 | } 58 | 59 | func (c Content) GzippedBase64Content() string { 60 | out, err := gzipcompressor.BytesToGzippedBase64String(c.bytes) 61 | if err != nil { 62 | return "" 63 | } 64 | return out 65 | } 66 | -------------------------------------------------------------------------------- /provisioner/remote_file_spec.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func (f RemoteFileSpec) Load(loader *RemoteFileLoader) (*RemoteFile, error) { 9 | return loader.Load(f) 10 | } 11 | 12 | func (f RemoteFileSpec) BaseName() string { 13 | return filepath.Base(f.Source.Path) 14 | } 15 | 16 | func (f RemoteFileSpec) FileMode() *os.FileMode { 17 | if f.Permissions != 0 { 18 | mode := os.FileMode(f.Permissions) 19 | return &mode 20 | } 21 | return nil 22 | } 23 | 24 | func (f RemoteFileSpec) IsBinary() bool { 25 | return f.Type == "binary" 26 | } 27 | -------------------------------------------------------------------------------- /provisioner/transfer.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "fmt" 5 | "github.com/aws/aws-sdk-go/aws" 6 | "github.com/aws/aws-sdk-go/service/s3" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | type S3ObjectPutter interface { 12 | PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) 13 | } 14 | 15 | func (t TransferredFile) S3URI() string { 16 | return fmt.Sprintf("%s/%s", t.s3DirURI, t.BaseName()) 17 | } 18 | 19 | func (t TransferredFile) ReceiveCommand() string { 20 | return fmt.Sprintf(`aws s3 cp %s %s`, t.S3URI(), t.Path) 21 | } 22 | 23 | func (t TransferredFile) Send(client S3ObjectPutter) error { 24 | opened, err := os.Open(t.Source.Path) 25 | if err != nil { 26 | return err 27 | } 28 | defer opened.Close() 29 | 30 | splits1 := strings.Split(t.s3DirURI, "s3://") 31 | s3prefix := splits1[1] 32 | 33 | splits := strings.SplitN(s3prefix, "/", 2) 34 | bucket := splits[0] 35 | prefix := splits[1] 36 | 37 | fmt.Fprintf(os.Stderr, "putting %s onto %s with prefix %s\n", t.BaseName(), bucket, prefix) 38 | _, err = client.PutObject(&s3.PutObjectInput{ 39 | Bucket: aws.String(bucket), 40 | Key: aws.String(prefix + "/" + t.BaseName()), 41 | Body: opened, 42 | }) 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /test/helper/encrypt_service.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "github.com/aws/aws-sdk-go/service/kms" 4 | 5 | type DummyEncryptService struct{} 6 | 7 | func (d DummyEncryptService) Encrypt(input *kms.EncryptInput) (*kms.EncryptOutput, error) { 8 | output := kms.EncryptOutput{ 9 | CiphertextBlob: input.Plaintext, 10 | } 11 | return &output, nil 12 | } 13 | -------------------------------------------------------------------------------- /test/helper/plugin.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "syscall" 9 | "testing" 10 | ) 11 | 12 | type TestPlugin struct { 13 | Name string 14 | Yaml string 15 | Files map[string]string 16 | } 17 | 18 | func isNotExist(err error) bool { 19 | return err == syscall.ENOENT || err == os.ErrNotExist 20 | } 21 | 22 | func WithPlugins(t *testing.T, plugins []TestPlugin, fn func()) { 23 | dir, err := filepath.Abs("./") 24 | if err != nil { 25 | panic(err) 26 | } 27 | pluginsDir := path.Join(dir, "plugins") 28 | 29 | //if _, err := os.Stat(pluginsDir); isNotExist(err) { 30 | if err := os.Mkdir(pluginsDir, 0755); err != nil { 31 | t.Errorf("%+v", err) 32 | t.FailNow() 33 | } 34 | //} 35 | 36 | defer os.RemoveAll(pluginsDir) 37 | 38 | for _, p := range plugins { 39 | pluginDir := path.Join(pluginsDir, p.Name) 40 | if err := os.Mkdir(pluginDir, 0755); err != nil { 41 | t.Errorf("%+v", err) 42 | t.FailNow() 43 | } 44 | 45 | pluginYamlFile := path.Join(pluginDir, "plugin.yaml") 46 | if err := ioutil.WriteFile(pluginYamlFile, []byte(p.Yaml), 0644); err != nil { 47 | t.Errorf("%+v", err) 48 | t.FailNow() 49 | } 50 | 51 | files := p.Files 52 | if files == nil { 53 | files = map[string]string{} 54 | } 55 | for relFilePath, content := range files { 56 | absFilePath := filepath.Join(pluginDir, relFilePath) 57 | absDirPath := filepath.Dir(absFilePath) 58 | if err := os.MkdirAll(absDirPath, 0755); err != nil { 59 | t.Errorf("%+v", err) 60 | t.FailNow() 61 | } 62 | if err := ioutil.WriteFile(absFilePath, []byte(content), 0644); err != nil { 63 | t.Errorf("%+v", err) 64 | t.FailNow() 65 | } 66 | } 67 | } 68 | 69 | fn() 70 | } 71 | -------------------------------------------------------------------------------- /test/helper/s3.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/aws/aws-sdk-go/service/s3" 7 | ) 8 | 9 | type DummyS3ObjectPutterService struct { 10 | ExpectedBucket string 11 | ExpectedKey string 12 | ExpectedBody string 13 | ExpectedContentType string 14 | ExpectedContentLength int64 15 | } 16 | 17 | func (s3Svc DummyS3ObjectPutterService) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) { 18 | 19 | if s3Svc.ExpectedContentLength != *input.ContentLength { 20 | return nil, fmt.Errorf( 21 | "expected content length does not match supplied content length\nexpected=%v, supplied=%v", 22 | s3Svc.ExpectedContentLength, 23 | input.ContentLength, 24 | ) 25 | } 26 | 27 | if s3Svc.ExpectedBucket != *input.Bucket { 28 | return nil, fmt.Errorf( 29 | "expected bucket does not match supplied bucket\nexpected=%v, supplied=%v", 30 | s3Svc.ExpectedBucket, 31 | input.Bucket, 32 | ) 33 | } 34 | 35 | if s3Svc.ExpectedKey != *input.Key { 36 | return nil, fmt.Errorf( 37 | "expected key does not match supplied key\nexpected=%v, supplied=%v", 38 | s3Svc.ExpectedKey, 39 | *input.Key, 40 | ) 41 | } 42 | 43 | if s3Svc.ExpectedContentType != *input.ContentType { 44 | return nil, fmt.Errorf( 45 | "expected content type does not match supplied content type\nexpected=%v, supplied=%v", 46 | s3Svc.ExpectedContentType, 47 | input.ContentType, 48 | ) 49 | } 50 | 51 | buf := new(bytes.Buffer) 52 | buf.ReadFrom(input.Body) 53 | suppliedBody := buf.String() 54 | 55 | if s3Svc.ExpectedBody != suppliedBody { 56 | return nil, fmt.Errorf( 57 | "expected body does not match supplied body\nexpected=%v, supplied=%v", 58 | s3Svc.ExpectedBody, 59 | suppliedBody, 60 | ) 61 | } 62 | 63 | resp := &s3.PutObjectOutput{} 64 | 65 | return resp, nil 66 | } 67 | -------------------------------------------------------------------------------- /tmpl/text_to_cfn_expr.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | func TextToCfnExprTokens(src string) []json.RawMessage { 12 | tokens := []json.RawMessage{} 13 | i := 0 14 | strStart := -1 15 | 16 | finishStr := func() { 17 | if strStart != -1 { 18 | bt, err := json.Marshal(src[strStart:i]) 19 | if err != nil { 20 | panic(err) 21 | } 22 | tokens = append(tokens, json.RawMessage(bt)) 23 | strStart = -1 24 | } 25 | } 26 | 27 | readExpr := func() { 28 | reader := strings.NewReader(src[i:]) 29 | remainings := int64(len(src[i:])) 30 | dec := json.NewDecoder(reader) 31 | expr := "" 32 | var j int64 33 | for { 34 | t, err := dec.Token() 35 | if err == io.EOF { 36 | break 37 | } 38 | r := dec.Buffered().(*bytes.Reader) 39 | if err != nil { 40 | break 41 | } 42 | j = remainings - r.Size() 43 | //fmt.Printf("%T: %v %v %d %v\n", t, t, err, j, dec.More()) 44 | expr = expr + fmt.Sprintf("%v", t) 45 | if t == "}" && !dec.More() { 46 | break 47 | } 48 | } 49 | tokens = append(tokens, json.RawMessage(src[i:i+int(j)])) 50 | i = i + int(j) 51 | } 52 | 53 | starts := []string{`{"Ref":`, `{"Fn::`} 54 | 55 | Loop: 56 | for i < len(src) { 57 | if src[i] == '{' && i+6 < len(src) { 58 | for _, start := range starts { 59 | peek := src[i : i+len(start)] 60 | //fmt.Println(peek) 61 | if len(src) >= len(start) && peek == start { 62 | finishStr() 63 | 64 | readExpr() 65 | continue Loop 66 | } 67 | } 68 | } 69 | if strStart == -1 { 70 | strStart = i 71 | } 72 | i++ 73 | } 74 | finishStr() 75 | return tokens 76 | } 77 | 78 | //func TextToCfnExpr(src string) string { 79 | // tokens := TextToCfnExprTokens(src) 80 | // return fmt.Sprintf(`{"Fn::Join": ["", [%s]]}`, strings.Join(tokens, ", ")) 81 | //} 82 | -------------------------------------------------------------------------------- /tmpl/tmpl.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | type Context struct { 13 | BasePath string 14 | FsReadFile func(string) ([]byte, error) 15 | } 16 | 17 | func New() *Context { 18 | dir, err := os.Getwd() 19 | if err != nil { 20 | panic(err) 21 | } 22 | return &Context{ 23 | BasePath: dir, 24 | FsReadFile: ioutil.ReadFile, 25 | } 26 | } 27 | 28 | func (c *Context) CreateFuncMap() template.FuncMap { 29 | return template.FuncMap{ 30 | "readFile": c.ReadFile, 31 | 32 | "indent": indent, 33 | } 34 | } 35 | 36 | func (c *Context) ReadFile(filename string) (string, error) { 37 | path := filepath.Join(c.BasePath, filename) 38 | 39 | bytes, err := c.FsReadFile(path) 40 | if err != nil { 41 | return "", err 42 | } 43 | return string(bytes), nil 44 | } 45 | 46 | func indent(spaces int, input string) string { 47 | idt := strings.Repeat(" ", spaces) 48 | 49 | buf := new(bytes.Buffer) 50 | for _, line := range strings.Split(input, "\n") { 51 | _, err := buf.WriteString(idt + line + "\n") 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | return buf.String() 57 | } 58 | -------------------------------------------------------------------------------- /tmpl/tmpl_test.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestTextToCfnExprTokens(t *testing.T) { 10 | testCases := []struct { 11 | expected []json.RawMessage 12 | src string 13 | }{ 14 | { 15 | expected: []json.RawMessage{json.RawMessage(`"foobar"`)}, 16 | src: `foobar`, 17 | }, 18 | { 19 | expected: []json.RawMessage{json.RawMessage(`"apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: aws-auth\ndata:\n mapRoles: |\n - rolearn: "`), 20 | json.RawMessage(`{"Fn::GetAtt": ["IAMRoleController", "Arn"]}`), 21 | }, 22 | src: `apiVersion: v1 23 | kind: ConfigMap 24 | metadata: 25 | name: aws-auth 26 | data: 27 | mapRoles: | 28 | - rolearn: {"Fn::GetAtt": ["IAMRoleController", "Arn"]}`, 29 | }, 30 | } 31 | 32 | for _, testCase := range testCases { 33 | actual := TextToCfnExprTokens(testCase.src) 34 | if !reflect.DeepEqual(testCase.expected, actual) { 35 | t.Errorf("unexpected result: expected=%+v, actual=%+v", testCase.expected, actual) 36 | } 37 | } 38 | } 39 | --------------------------------------------------------------------------------