├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── .golangci.yml ├── .zappr.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── SECURITY.md ├── api ├── cluster.go ├── cluster_status.go ├── cluster_test.go ├── cluster_version.go ├── cluster_version_test.go ├── node_pool.go ├── node_pool_test.go ├── problem.go └── test_util.go ├── channel ├── caching_source.go ├── combined.go ├── combined_test.go ├── config_source.go ├── directory.go ├── directory_test.go ├── git.go ├── git_test.go ├── mock_source.go ├── simple_config.go └── test_util.go ├── cmd └── clm │ └── main.go ├── config ├── config.go ├── include_exclude.go └── include_exclude_test.go ├── controller ├── cluster_list.go ├── cluster_list_test.go ├── controller.go └── controller_test.go ├── delivery.yaml ├── docs ├── cluster-registry.yaml ├── images │ └── cluster-lifecycle-manager.svg └── proposals │ └── adr-002-apply-kube-system.md ├── go.mod ├── go.sum ├── pkg ├── aws │ ├── assume_role_provider.go │ ├── eks │ │ └── token.go │ ├── instance_info.go │ ├── instance_info_test.go │ ├── retryer.go │ ├── retryer_test.go │ └── session.go ├── cluster-registry │ ├── client │ │ ├── cluster_registry_client.go │ │ ├── clusters │ │ │ ├── clusters_client.go │ │ │ ├── create_cluster_parameters.go │ │ │ ├── create_cluster_responses.go │ │ │ ├── delete_cluster_parameters.go │ │ │ ├── delete_cluster_responses.go │ │ │ ├── get_cluster_parameters.go │ │ │ ├── get_cluster_responses.go │ │ │ ├── list_clusters_parameters.go │ │ │ ├── list_clusters_responses.go │ │ │ ├── update_cluster_parameters.go │ │ │ └── update_cluster_responses.go │ │ ├── config_items │ │ │ ├── add_or_update_config_item_parameters.go │ │ │ ├── add_or_update_config_item_responses.go │ │ │ ├── config_items_client.go │ │ │ ├── delete_config_item_parameters.go │ │ │ └── delete_config_item_responses.go │ │ ├── infrastructure_accounts │ │ │ ├── create_infrastructure_account_parameters.go │ │ │ ├── create_infrastructure_account_responses.go │ │ │ ├── get_infrastructure_account_parameters.go │ │ │ ├── get_infrastructure_account_responses.go │ │ │ ├── infrastructure_accounts_client.go │ │ │ ├── list_infrastructure_accounts_parameters.go │ │ │ ├── list_infrastructure_accounts_responses.go │ │ │ ├── update_infrastructure_account_parameters.go │ │ │ └── update_infrastructure_account_responses.go │ │ ├── node_pool_config_items │ │ │ ├── add_or_update_node_pool_config_item_parameters.go │ │ │ ├── add_or_update_node_pool_config_item_responses.go │ │ │ ├── delete_node_pool_config_item_parameters.go │ │ │ ├── delete_node_pool_config_item_responses.go │ │ │ └── node_pool_config_items_client.go │ │ └── node_pools │ │ │ ├── create_or_update_node_pool_parameters.go │ │ │ ├── create_or_update_node_pool_responses.go │ │ │ ├── delete_node_pool_parameters.go │ │ │ ├── delete_node_pool_responses.go │ │ │ ├── list_node_pools_parameters.go │ │ │ ├── list_node_pools_responses.go │ │ │ ├── node_pools_client.go │ │ │ ├── update_node_pool_parameters.go │ │ │ └── update_node_pool_responses.go │ └── models │ │ ├── cluster.go │ │ ├── cluster_status.go │ │ ├── cluster_update.go │ │ ├── config_value.go │ │ ├── error.go │ │ ├── infrastructure_account.go │ │ ├── infrastructure_account_update.go │ │ ├── node_pool.go │ │ └── node_pool_update.go ├── credentials-loader │ ├── jwt │ │ ├── jwt.go │ │ └── jwt_test.go │ └── platformiam │ │ ├── file.go │ │ └── file_test.go ├── decrypter │ ├── decrypter.go │ ├── decrypter_test.go │ └── kms.go ├── kubernetes │ ├── kubernetes.go │ └── kubernetes_test.go ├── updatestrategy │ ├── aws_asg.go │ ├── aws_asg_test.go │ ├── aws_ec2.go │ ├── clc_update.go │ ├── clc_update_test.go │ ├── drain.go │ ├── drain_test.go │ ├── node_pool_manager.go │ ├── node_pool_manager_test.go │ ├── rolling_update.go │ ├── rolling_update_test.go │ ├── updatestrategy.go │ └── updatestrategy_test.go └── util │ ├── command │ ├── command.go │ └── command_test.go │ ├── copy.go │ ├── copy_test.go │ └── generics.go ├── provisioner ├── aws.go ├── aws_az.go ├── aws_az_test.go ├── aws_test.go ├── cf.go ├── clusterpy.go ├── clusterpy_test.go ├── hack.go ├── jwks.go ├── jwks_test.go ├── kubesystem.go ├── node_pools.go ├── provisioner.go ├── remote_files.go ├── remote_files_test.go ├── stdout.go ├── template.go ├── template_test.go ├── zalando_aws.go ├── zalando_eks.go └── zalando_eks_test.go ├── registry ├── file.go ├── http.go ├── http_test.go ├── registry.go ├── registry_test.go └── static.go └── tools.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | groups: 9 | all-go-mod-patch-and-minor: 10 | patterns: ["*"] 11 | update-types: ["patch", "minor"] 12 | ignore: 13 | # Ignore k8s and its transitives modules as they are upgraded manually 14 | - dependency-name: "k8s.io/*" 15 | update-types: ["version-update:semver-major", "version-update:semver-minor"] 16 | - package-ecosystem: docker 17 | directory: "/" 18 | schedule: 19 | interval: monthly 20 | time: "07:00" 21 | open-pull-requests-limit: 10 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push, pull_request] 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-go@v2 9 | with: 10 | go-version: '^1.24' 11 | - run: go version 12 | - run: go install github.com/mattn/goveralls@latest 13 | - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 14 | - run: make build.docker 15 | - run: make test 16 | env: 17 | GOEXPERIMENT: nocoverageredesign 18 | - run: make lint 19 | - run: goveralls -coverprofile=profile.cov -service=github 20 | env: 21 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | vendor/ 3 | .kube 4 | profile.cov 5 | .idea/ 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 10m 3 | 4 | linters-settings: 5 | golint: 6 | min-confidence: 0.9 7 | 8 | linters: 9 | disable-all: true 10 | enable: 11 | - staticcheck 12 | - ineffassign 13 | - revive 14 | - goimports 15 | - errcheck 16 | issues: 17 | exclude-rules: 18 | # Exclude some staticcheck messages 19 | - linters: 20 | - staticcheck 21 | text: "SA9003:" 22 | -------------------------------------------------------------------------------- /.zappr.yaml: -------------------------------------------------------------------------------- 1 | # for github.com 2 | approvals: 3 | groups: 4 | zalando: 5 | minimum: 2 6 | from: 7 | orgs: 8 | - "zalando" 9 | 10 | X-Zalando-Team: "teapot" 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **Thank you for your interest in making the project better and more useful. 4 | Your contributions are highly welcome.** 5 | 6 | There are multiple ways of getting involved: 7 | 8 | - [Report a bug](#report-a-bug) 9 | - [Suggest a feature](#suggest-a-feature) 10 | - [Contribute code](#contribute-code) 11 | 12 | Below are a few guidelines we would like you to follow. 13 | If you need help, please reach out to us: team-teapot@zalando.de 14 | 15 | ## Report a bug 16 | 17 | Reporting bugs is one of the best ways to contribute. Before creating a bug 18 | report, please check that an [issue](../../issues) reporting the same problem does 19 | not already exist. If there is an such an issue, you may add your information 20 | as a comment. 21 | 22 | To report a new bug you should open an issue that summarizes the bug and set 23 | the label to "bug". 24 | 25 | If you want to provide a fix along with your bug report: That is great! In this 26 | case please send us a pull request as described in section [Contribute 27 | Code](#contribute-code). 28 | 29 | ## Suggest a feature 30 | 31 | To request a new feature you should first check if the feature is already 32 | requested in one of the open [issues](../../issues). If not, then please open 33 | an [issue](../../issues/new) and summarize the desired functionality and its 34 | use case. Set the issue label to "feature". 35 | 36 | ## Contribute code 37 | 38 | This is a rough outline of what the workflow for code contributions looks like: 39 | - Check the list of open [issues](../../issues) Either assign an existing issue 40 | to yourself, or create a new one that you would like work on and discuss your 41 | ideas and use cases. 42 | - Fork the repository on GitHub 43 | - Create a topic branch from where you want to base your work. This is usually 44 | master. 45 | - Make commits of logical units. 46 | - Write good commit messages (see below). 47 | - Sign/sign off your commits: `git commit -s ..`. 48 | - Push your changes to a topic branch in your fork of the repository. 49 | - Submit a pull request to the repository. 50 | - Your pull request must receive a :thumbsup: from two [Maintainers](/MAINTAINERS). 51 | 52 | Thanks for your contributions! 53 | 54 | ### Code style 55 | 56 | Code is formatted with [gofmt](https://golang.org/cmd/gofmt/). Please run it 57 | on your code before making a pull request. The coding style suggested by the 58 | Golang community is the preferred one for the cases that are not covered by 59 | gofmt, see the [style 60 | doc](https://github.com/golang/go/wiki/CodeReviewComments) for details. 61 | 62 | ### Commit messages 63 | 64 | Your commit messages ideally can answer two questions: what changed and why. 65 | The subject line should feature the “what” and the body of the commit should 66 | describe the “why”. 67 | 68 | When creating a pull request, its description should reference the corresponding 69 | issue id. 70 | 71 | **Have fun and enjoy hacking!** 72 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=public.ecr.aws/amazonlinux/amazonlinux:2023-minimal 2 | FROM ${BASE_IMAGE} as kubectl_download 3 | 4 | ARG TARGETARCH 5 | 6 | RUN dnf install -y tar gzip && \ 7 | if [ "${TARGETARCH}" == "" ]; then TARGETARCH=amd64; fi; \ 8 | curl -L -s --fail "https://dl.k8s.io/v1.31.1/kubernetes-client-linux-${TARGETARCH}.tar.gz" -o "kubernetes-client-linux-${TARGETARCH}.tar.gz" && \ 9 | printf "609df79769237073275c2a3891e6581c9408da47293276fa12d0332fdef0d2f83bcbf2bea7bb64a9f18b1007ec6500af0ea7daabdcb1aca22d33f4f132a09c27 kubernetes-client-linux-amd64.tar.gz\nd2ac66cc7d48149db5ea17e8262eb1290d542d567a72661000275a24d3fca8c3ea3c8515ae6a19ed5d28e92829a07fb28093853c6ae74b2b946858e967709f09 kubernetes-client-linux-arm64.tar.gz" | grep "${TARGETARCH}" | sha512sum -c - && \ 10 | tar xvf "kubernetes-client-linux-${TARGETARCH}.tar.gz" --strip-components 3 kubernetes/client/bin/ && \ 11 | rm "kubernetes-client-linux-${TARGETARCH}.tar.gz" 12 | 13 | FROM ${BASE_IMAGE} 14 | LABEL maintainer="Team Teapot @ Zalando SE " 15 | 16 | ARG TARGETARCH 17 | 18 | # install dependencies 19 | RUN dnf install -y openssl git openssh-clients && dnf clean all 20 | 21 | COPY --from=kubectl_download /kubectl /usr/local/bin/ 22 | 23 | # add binary 24 | ADD build/linux/${TARGETARCH}/clm / 25 | 26 | CMD ["--help"] 27 | ENTRYPOINT ["/clm"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Zalando SE 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. 22 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Mikkel Larsen 2 | Martin Linkhorst 3 | Alexey Ermakov 4 | Team Teapot 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean lint build.local build.linux build.osx build.docker build.push 2 | 3 | BINARY ?= clm 4 | VERSION ?= $(shell git describe --tags --always --dirty) 5 | IMAGE ?= registry-write.opensource.zalan.do/teapot/cluster-lifecycle-manager 6 | TAG ?= $(VERSION) 7 | SOURCES = $(shell find . -name '*.go') 8 | GO ?= go 9 | SPEC = docs/cluster-registry.yaml 10 | CR_CLIENT = pkg/cluster-registry 11 | DOCKERFILE ?= Dockerfile 12 | GOPKGS = $(shell $(GO) list ./...) 13 | GO_SWAGGER = ./build/swagger 14 | BUILD_FLAGS ?= -v 15 | LDFLAGS ?= -X main.version=$(VERSION) -w -s 16 | 17 | default: build.local 18 | 19 | clean: 20 | rm -rf build 21 | rm -rf $(CR_CLIENT) 22 | rm -rf $(AWS_INSTANCE_DATA) 23 | 24 | test: $(CR_CLIENT) $(AWS_INSTANCE_DATA) 25 | $(GO) test -v -race -coverprofile=profile.cov $(GOPKGS) 26 | $(GO) vet -v $(GOPKGS) 27 | 28 | lint: $(CR_CLIENT) $(SOURCES) $(AWS_INSTANCE_DATA) 29 | $(GO) mod download 30 | golangci-lint -v run ./... 31 | 32 | fmt: 33 | $(GO) fmt $(GOPKGS) 34 | 35 | $(AWS_DATA_SRC): 36 | mkdir -p $(dir $@) 37 | curl -L -s --fail https://www.ec2instances.info/instances.json | jq '[.[] | {instance_type, vCPU, memory, storage: (if .storage == null then null else .storage | {devices, size, nvme_ssd} end)}] | sort_by(.instance_type)' > "$@" 38 | 39 | $(CR_CLIENT): $(SPEC) 40 | mkdir -p $@ 41 | go run github.com/go-swagger/go-swagger/cmd/swagger generate client --name cluster-registry --principal oauth.User --spec docs/cluster-registry.yaml --target ./$(CR_CLIENT) 42 | 43 | build.local: build/$(BINARY) 44 | build.linux: build/linux/$(BINARY) 45 | build.osx: build/osx/$(BINARY) 46 | build.linux.amd64: build/linux/amd64/$(BINARY) 47 | build.linux.arm64: build/linux/arm64/$(BINARY) 48 | 49 | build/$(BINARY): $(CR_CLIENT) $(SOURCES) $(AWS_INSTANCE_DATA) 50 | CGO_ENABLED=0 $(GO) build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" ./cmd/$(BINARY) 51 | 52 | build/linux/$(BINARY): $(CR_CLIENT) $(SOURCES) $(AWS_INSTANCE_DATA) 53 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build $(BUILD_FLAGS) -o build/linux/$(BINARY) -ldflags "$(LDFLAGS)" ./cmd/$(BINARY) 54 | 55 | build/linux/amd64/%: go.mod $(SOURCES) 56 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build $(BUILD_FLAGS) -o build/linux/amd64/$(notdir $@) -ldflags "$(LDFLAGS)" ./cmd/$(notdir $@) 57 | 58 | build/linux/arm64/%: go.mod $(SOURCES) 59 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 $(GO) build $(BUILD_FLAGS) -o build/linux/arm64/$(notdir $@) -ldflags "$(LDFLAGS)" ./cmd/$(notdir $@) 60 | 61 | build/osx/$(BINARY): $(CR_CLIENT) $(SOURCES) $(AWS_INSTANCE_DATA) 62 | GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 $(GO) build $(BUILD_FLAGS) -o build/osx/$(BINARY) -ldflags "$(LDFLAGS)" ./cmd/$(BINARY) 63 | 64 | build.docker: build.linux 65 | docker build --rm -t "$(IMAGE):$(TAG)" -f $(DOCKERFILE) --build-arg TARGETARCH= . 66 | 67 | build.push: build.docker 68 | docker push "$(IMAGE):$(TAG)" 69 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | We acknowledge that every line of code that we write may potentially contain security issues. 2 | We are trying to deal with it responsibly and provide patches as quickly as possible. 3 | 4 | We host our bug bounty program on HackerOne, it is currently private, therefore if you would like to report a vulnerability and get rewarded for it, please ask to join our program by filling this form: 5 | 6 | https://corporate.zalando.com/en/services-and-contact#security-form 7 | 8 | You can also send you report via this form if you do not want to join our bug bounty program and just want to report a vulnerability or security issue. 9 | -------------------------------------------------------------------------------- /api/cluster_status.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ClusterStatus describes the status of a cluster. 4 | type ClusterStatus struct { 5 | CurrentVersion string `json:"current_version" yaml:"current_version"` 6 | LastVersion string `json:"last_version" yaml:"last_version"` 7 | NextVersion string `json:"next_version" yaml:"next_version"` 8 | Problems []*Problem `json:"problems" yaml:"problems"` 9 | } 10 | -------------------------------------------------------------------------------- /api/cluster_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/sirupsen/logrus" 11 | "github.com/stretchr/testify/require" 12 | "github.com/zalando-incubator/cluster-lifecycle-manager/channel" 13 | ) 14 | 15 | func fieldNames(value interface{}) ([]string, error) { 16 | v := reflect.ValueOf(value) 17 | 18 | if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { 19 | return nil, fmt.Errorf("invalid value type, expected pointer to struct: %s", v.Kind()) 20 | } 21 | 22 | v = v.Elem() 23 | 24 | result := make([]string, v.Type().NumField()) 25 | for i := 0; i < v.NumField(); i++ { 26 | result[i] = v.Type().Field(i).Name 27 | } 28 | return result, nil 29 | } 30 | 31 | func permute(value interface{}, field string) error { 32 | v := reflect.ValueOf(value) 33 | 34 | if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { 35 | return fmt.Errorf("invalid value type, expected pointer to struct: %s", v.Kind()) 36 | } 37 | 38 | fld := v.Elem().FieldByName(field) 39 | switch fld.Type().Kind() { 40 | case reflect.String: 41 | fld.SetString("") 42 | case reflect.Int, reflect.Int32, reflect.Int64: 43 | fld.SetInt(123456) 44 | case reflect.Map: 45 | switch v := fld.Interface().(type) { 46 | case map[string]string: 47 | v[""] = "permuted_value" 48 | default: 49 | return fmt.Errorf("invalid map type for %s", field) 50 | } 51 | case reflect.Slice: 52 | switch fld.Interface().(type) { 53 | case []string: 54 | fld.Set(reflect.ValueOf([]string{"a", "b", "c"})) 55 | default: 56 | return fmt.Errorf("invalid slice type for %s", field) 57 | } 58 | default: 59 | return fmt.Errorf("unsupported type: %s", fld.Type()) 60 | } 61 | 62 | return nil 63 | } 64 | 65 | type mockVersion struct{} 66 | 67 | func (v mockVersion) ID() string { 68 | return "git-commit-hash" 69 | } 70 | 71 | func (v mockVersion) Get(_ context.Context, _ *logrus.Entry) (channel.Config, error) { 72 | return nil, errors.New("unsupported") 73 | } 74 | 75 | func TestVersion(t *testing.T) { 76 | commitHash := mockVersion{} 77 | 78 | version, err := SampleCluster().Version(commitHash) 79 | require.NoError(t, err) 80 | 81 | // cluster fields 82 | fields, err := fieldNames(SampleCluster()) 83 | require.NoError(t, err) 84 | 85 | for _, field := range fields { 86 | if field == "Alias" || field == "NodePools" || field == "Owner" || field == "AccountName" || field == "Status" { 87 | continue 88 | } 89 | 90 | cluster := SampleCluster() 91 | err := permute(cluster, field) 92 | require.NoError(t, err, "cluster field: %s", field) 93 | 94 | newVersion, err := cluster.Version(commitHash) 95 | 96 | require.NoError(t, err, "cluster field: %s", field) 97 | require.NotEqual(t, version, newVersion, "cluster field: %s", field) 98 | } 99 | 100 | // node pool fields 101 | fields, err = fieldNames(SampleCluster().NodePools[0]) 102 | require.NoError(t, err) 103 | 104 | for _, field := range fields { 105 | if field == "InstanceType" { 106 | continue 107 | } 108 | 109 | cluster := SampleCluster() 110 | err := permute(cluster.NodePools[0], field) 111 | require.NoError(t, err, "node pool field: %s", field) 112 | 113 | newVersion, err := cluster.Version(commitHash) 114 | 115 | require.NoError(t, err, "node pool field: %s", field) 116 | require.NotEqual(t, version, newVersion, "cluster field: %s", field) 117 | } 118 | } 119 | 120 | func TestName(t *testing.T) { 121 | cluster := &Cluster{ 122 | ID: "aws:123456789012:eu-central-1:test-cluster", 123 | LocalID: "test-cluster", 124 | Provider: ZalandoAWSProvider, 125 | } 126 | 127 | require.Equal(t, cluster.ID, cluster.Name()) 128 | 129 | cluster = &Cluster{ 130 | ID: "aws:123456789012:eu-central-1:test-cluster", 131 | LocalID: "test-cluster", 132 | Provider: ZalandoEKSProvider, 133 | } 134 | 135 | require.Equal(t, cluster.LocalID, cluster.Name()) 136 | } 137 | -------------------------------------------------------------------------------- /api/cluster_version.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // ClusterVersion is a combination of configuration version from the configuration repository 8 | // and a hash of cluster's metadata. 9 | type ClusterVersion struct { 10 | ConfigVersion string 11 | ClusterHash string 12 | } 13 | 14 | // ParseVersion parses a version string into a ConfigVersion. Invalid version strings are parsed 15 | // into empty ClusterVersion structs. 16 | func ParseVersion(version string) *ClusterVersion { 17 | tokens := strings.Split(version, "#") 18 | if len(tokens) != 2 { 19 | return &ClusterVersion{ 20 | ConfigVersion: "", 21 | ClusterHash: "", 22 | } 23 | } 24 | return &ClusterVersion{ 25 | ConfigVersion: tokens[0], 26 | ClusterHash: tokens[1], 27 | } 28 | } 29 | 30 | func (version *ClusterVersion) String() string { 31 | if version == nil { 32 | return "" 33 | } 34 | 35 | return version.ConfigVersion + "#" + version.ClusterHash 36 | } 37 | -------------------------------------------------------------------------------- /api/cluster_version_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestParseVersion(t *testing.T) { 10 | for _, tc := range []struct { 11 | name string 12 | version string 13 | expected string 14 | }{ 15 | { 16 | name: "empty", 17 | version: "", 18 | expected: "#", 19 | }, 20 | { 21 | name: "simple", 22 | version: "foo#bar", 23 | expected: "foo#bar", 24 | }, 25 | { 26 | name: "missing hash", 27 | version: "foo", 28 | expected: "#", 29 | }, 30 | { 31 | name: "missing version", 32 | version: "#bar", 33 | expected: "#bar", 34 | }, 35 | } { 36 | t.Run(tc.name, func(t *testing.T) { 37 | version := ParseVersion(tc.version) 38 | result := version.String() 39 | require.Equal(t, tc.expected, result) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/node_pool.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "strings" 5 | 6 | log "github.com/sirupsen/logrus" 7 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/kubernetes" 8 | "gopkg.in/yaml.v2" 9 | corev1 "k8s.io/api/core/v1" 10 | v1 "k8s.io/client-go/applyconfigurations/core/v1" 11 | ) 12 | 13 | // NodePool describes a node pool in a kubernetes cluster. 14 | type NodePool struct { 15 | DiscountStrategy string `json:"discount_strategy" yaml:"discount_strategy"` 16 | InstanceTypes []string `json:"instance_types" yaml:"instance_types"` 17 | Name string `json:"name" yaml:"name"` 18 | Profile string `json:"profile" yaml:"profile"` 19 | MinSize int64 `json:"min_size" yaml:"min_size"` 20 | MaxSize int64 `json:"max_size" yaml:"max_size"` 21 | ConfigItems map[string]string `json:"config_items" yaml:"config_items"` 22 | 23 | // Deprecated, only kept here so the existing clusters don't break 24 | InstanceType string 25 | } 26 | 27 | func (np NodePool) IsSpot() bool { 28 | return np.DiscountStrategy == "spot_max_price" || np.DiscountStrategy == "spot" 29 | } 30 | 31 | func (np NodePool) IsMaster() bool { 32 | return strings.Contains(np.Profile, "master") 33 | } 34 | 35 | func (np NodePool) IsKarpenter() bool { 36 | return np.Profile == "worker-karpenter" 37 | } 38 | 39 | func (np NodePool) Taints() []*corev1.Taint { 40 | conf, exist := np.ConfigItems["taints"] 41 | if !exist { 42 | return nil 43 | } 44 | var taints []*corev1.Taint 45 | for _, t := range strings.Split(conf, ",") { 46 | taint, err := kubernetes.ParseTaint(t) 47 | if err == nil { 48 | taints = append(taints, taint) 49 | } 50 | } 51 | return taints 52 | } 53 | 54 | func (np NodePool) KarpenterRequirements() []v1.NodeSelectorRequirementApplyConfiguration { 55 | conf, exist := np.ConfigItems["requirements"] 56 | if !exist { 57 | return nil 58 | } 59 | var requirements []v1.NodeSelectorRequirementApplyConfiguration 60 | err := yaml.Unmarshal([]byte(conf), &requirements) 61 | if err != nil { 62 | log.Errorf("Error unmarshalling requirements: %v", err) 63 | } 64 | return requirements 65 | } 66 | 67 | func (np NodePool) KarpenterInstanceTypeStrategy() string { 68 | if len(np.InstanceTypes) == 1 && np.InstanceTypes[0] == "not-specified" { 69 | return "not-specified" 70 | } 71 | if len(np.InstanceTypes) == 1 && np.InstanceTypes[0] == "default-for-karpenter" { 72 | return "default-for-karpenter" 73 | } 74 | return "custom" 75 | } 76 | -------------------------------------------------------------------------------- /api/node_pool_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIsKarpenter(t *testing.T) { 10 | pool := NodePool{Profile: "worker-karpenter"} 11 | require.True(t, pool.IsKarpenter()) 12 | } 13 | -------------------------------------------------------------------------------- /api/problem.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // Problem describes a problem. 4 | type Problem struct { 5 | Detail string `json:"detail" yaml:"detail"` 6 | Instance string `json:"instance" yaml:"instance"` 7 | Status int32 `json:"status" yaml:"status"` 8 | Title string `json:"title" yaml:"title"` 9 | Type string `json:"type" yaml:"type"` 10 | } 11 | -------------------------------------------------------------------------------- /api/test_util.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | func SampleCluster() *Cluster { 4 | return &Cluster{ 5 | ID: "aws:123456789012:eu-central-1:kube-1", 6 | InfrastructureAccount: "aws:123456789012", 7 | LocalID: "kube-1", 8 | APIServerURL: "https://kube-1.foo.example.org/", 9 | Channel: "alpha", 10 | Environment: "production", 11 | CriticalityLevel: 1, 12 | LifecycleStatus: "ready", 13 | Provider: "zalando-aws", 14 | Region: "eu-central-1", 15 | ConfigItems: map[string]string{ 16 | "product_x_key": "abcde", 17 | "product_y_key": "12345", 18 | }, 19 | NodePools: []*NodePool{ 20 | { 21 | Name: "master-default", 22 | Profile: "master-default", 23 | InstanceTypes: []string{"m4.large"}, 24 | DiscountStrategy: "none", 25 | MinSize: 2, 26 | MaxSize: 2, 27 | ConfigItems: map[string]string{}, 28 | }, 29 | { 30 | Name: "worker-default", 31 | Profile: "worker-default", 32 | InstanceTypes: []string{"m5.large", "m5.2xlarge"}, 33 | DiscountStrategy: "none", 34 | MinSize: 3, 35 | MaxSize: 21, 36 | ConfigItems: map[string]string{ 37 | "taints": "my-taint=:NoSchedule", 38 | }, 39 | }, 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /channel/caching_source.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | // CachingSource caches resolved versions until the next Update() 11 | type CachingSource struct { 12 | cache map[string]ConfigVersion 13 | target ConfigSource 14 | } 15 | 16 | func NewCachingSource(target ConfigSource) *CachingSource { 17 | return &CachingSource{ 18 | cache: make(map[string]ConfigVersion), 19 | target: target, 20 | } 21 | } 22 | 23 | func (c *CachingSource) Name() string { 24 | return c.target.Name() 25 | } 26 | 27 | func (c *CachingSource) Update(ctx context.Context, logger *logrus.Entry) error { 28 | c.cache = make(map[string]ConfigVersion) 29 | return c.target.Update(ctx, logger) 30 | } 31 | 32 | func (c *CachingSource) Version(channel string, overrides map[string]string) (ConfigVersion, error) { 33 | cacheKey := strings.Builder{} 34 | cacheKey.WriteString(channel) 35 | cacheKey.WriteRune('\x00') 36 | for k, v := range overrides { 37 | cacheKey.WriteString(k) 38 | cacheKey.WriteRune('\x00') 39 | cacheKey.WriteString(v) 40 | cacheKey.WriteRune('\x00') 41 | } 42 | 43 | if cached, ok := c.cache[cacheKey.String()]; ok { 44 | return cached, nil 45 | } 46 | res, err := c.target.Version(channel, overrides) 47 | if err != nil { 48 | return nil, err 49 | } 50 | c.cache[cacheKey.String()] = res 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /channel/combined.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | type CombinedSource struct { 13 | sources []ConfigSource 14 | } 15 | 16 | type combinedVersion struct { 17 | owner *CombinedSource 18 | versions []ConfigVersion 19 | } 20 | 21 | type combinedConfig struct { 22 | owner *CombinedSource 23 | configs []Config 24 | } 25 | 26 | func NewCombinedSource(sources []ConfigSource) (ConfigSource, error) { 27 | names := make(map[string]struct{}) 28 | for _, src := range sources { 29 | if _, ok := names[src.Name()]; ok { 30 | return nil, fmt.Errorf("duplicate config source name: %s", src.Name()) 31 | } 32 | names[src.Name()] = struct{}{} 33 | } 34 | 35 | return &CombinedSource{ 36 | sources: sources, 37 | }, nil 38 | } 39 | 40 | func (c *CombinedSource) Name() string { 41 | return "" 42 | } 43 | 44 | func (c *CombinedSource) Update(ctx context.Context, logger *logrus.Entry) error { 45 | for _, source := range c.sources { 46 | err := source.Update(ctx, logger) 47 | if err != nil { 48 | return fmt.Errorf("error while updating source %s: %v", source.Name(), err) 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (c *CombinedSource) Version(channel string, overrides map[string]string) (ConfigVersion, error) { 56 | versions := make([]ConfigVersion, len(c.sources)) 57 | for i, source := range c.sources { 58 | sourceChannel := channel 59 | if overrideChannel, ok := overrides[source.Name()]; ok { 60 | sourceChannel = overrideChannel 61 | } 62 | 63 | version, err := source.Version(sourceChannel, nil) 64 | if err != nil { 65 | return nil, fmt.Errorf("unable to determine version for source %s: %v", source.Name(), err) 66 | } 67 | versions[i] = version 68 | } 69 | 70 | return &combinedVersion{ 71 | owner: c, 72 | versions: versions, 73 | }, nil 74 | } 75 | 76 | func (c *CombinedSource) sourceName(pos int) string { 77 | return c.sources[pos].Name() 78 | } 79 | 80 | func (v *combinedVersion) ID() string { 81 | ids := make([]string, len(v.versions)) 82 | for i, version := range v.versions { 83 | ids[i] = fmt.Sprintf("%s=%s", v.owner.sourceName(i), version.ID()) 84 | } 85 | return strings.Join(ids, ";") 86 | } 87 | 88 | func (v *combinedVersion) Get(ctx context.Context, logger *logrus.Entry) (Config, error) { 89 | configs := make([]Config, len(v.versions)) 90 | for i, version := range v.versions { 91 | config, err := version.Get(ctx, logger) 92 | if err != nil { 93 | return nil, fmt.Errorf("unable to checkout version %s for source %s: %v", version.ID(), v.owner.sourceName(i), err) 94 | } 95 | configs[i] = config 96 | } 97 | 98 | return &combinedConfig{ 99 | owner: v.owner, 100 | configs: configs, 101 | }, nil 102 | } 103 | 104 | func (c *combinedConfig) mainConfig() (Config, error) { 105 | if len(c.configs) == 0 { 106 | return nil, errors.New("no configs found") 107 | } 108 | return c.configs[0], nil 109 | } 110 | 111 | func (c *combinedConfig) StackManifest(manifestName string) (Manifest, error) { 112 | mainConfig, err := c.mainConfig() 113 | if err != nil { 114 | return Manifest{}, err 115 | } 116 | return mainConfig.StackManifest(manifestName) 117 | } 118 | 119 | func (c *combinedConfig) EtcdManifest(manifestName string) (Manifest, error) { 120 | mainConfig, err := c.mainConfig() 121 | if err != nil { 122 | return Manifest{}, err 123 | } 124 | return mainConfig.EtcdManifest(manifestName) 125 | } 126 | 127 | func (c *combinedConfig) NodePoolManifest(profileName string, manifestName string) (Manifest, error) { 128 | mainConfig, err := c.mainConfig() 129 | if err != nil { 130 | return Manifest{}, err 131 | } 132 | return mainConfig.NodePoolManifest(profileName, manifestName) 133 | } 134 | 135 | func (c *combinedConfig) DefaultsManifests() ([]Manifest, error) { 136 | var result []Manifest 137 | for i, config := range c.configs { 138 | configs, err := config.DefaultsManifests() 139 | if err != nil { 140 | return nil, fmt.Errorf("unable to get defaults for source %s: %v", c.owner.sourceName(i), err) 141 | } 142 | result = append(result, configs...) 143 | } 144 | return result, nil 145 | } 146 | 147 | func (c *combinedConfig) DeletionsManifests() ([]Manifest, error) { 148 | var result []Manifest 149 | for i, config := range c.configs { 150 | configs, err := config.DeletionsManifests() 151 | if err != nil { 152 | return nil, fmt.Errorf("unable to get deletions for source %s: %v", c.owner.sourceName(i), err) 153 | } 154 | result = append(result, configs...) 155 | } 156 | return result, nil 157 | } 158 | 159 | func (c *combinedConfig) Components() ([]Component, error) { 160 | var result []Component 161 | 162 | for i, config := range c.configs { 163 | components, err := config.Components() 164 | if err != nil { 165 | return nil, fmt.Errorf("unable to get components for source %s: %v", c.owner.sourceName(i), err) 166 | } 167 | for _, component := range components { 168 | result = append(result, Component{ 169 | Name: fmt.Sprintf("%s/%s", c.owner.sourceName(i), component.Name), 170 | Manifests: component.Manifests, 171 | }) 172 | } 173 | } 174 | 175 | return result, nil 176 | } 177 | 178 | func (c *combinedConfig) Delete() error { 179 | var res error 180 | 181 | for i, config := range c.configs { 182 | err := config.Delete() 183 | if err != nil { 184 | logrus.Warnf("Unable to delete config for source %s: %v", c.owner.sourceName(i), err) 185 | res = err 186 | } 187 | } 188 | 189 | return res 190 | } 191 | -------------------------------------------------------------------------------- /channel/combined_test.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | log "github.com/sirupsen/logrus" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestCombinedSourceOverrides(t *testing.T) { 13 | main := &mockSource{ 14 | name: "main", 15 | validChannels: []string{"master"}, 16 | } 17 | secondary := &mockSource{ 18 | name: "secondary", 19 | validChannels: []string{"alternate"}, 20 | } 21 | 22 | combined, err := NewCombinedSource([]ConfigSource{main, secondary}) 23 | require.NoError(t, err) 24 | 25 | version, err := combined.Version("master", map[string]string{"secondary": "alternate"}) 26 | require.NoError(t, err) 27 | require.Equal(t, "main=master;secondary=alternate", version.ID()) 28 | 29 | _, err = combined.Version("missing", map[string]string{"secondary": "alternate"}) 30 | require.Error(t, err) 31 | 32 | _, err = combined.Version("master", map[string]string{"secondary": "missing"}) 33 | require.Error(t, err) 34 | } 35 | 36 | func TestCombinedSource(t *testing.T) { 37 | logger := log.StandardLogger().WithFields(map[string]interface{}{}) 38 | 39 | mainDir := CreateTempDir(t) 40 | defer os.RemoveAll(mainDir) 41 | 42 | secondaryDir := CreateTempDir(t) 43 | defer os.RemoveAll(secondaryDir) 44 | 45 | mainSrc, err := NewDirectory("main", mainDir) 46 | require.NoError(t, err) 47 | 48 | secondarySrc, err := NewDirectory("secondary", secondaryDir) 49 | require.NoError(t, err) 50 | 51 | SetupConfig( 52 | t, mainDir, 53 | map[string]string{ 54 | "cluster/manifests/example1/config.yaml": "example1-config-main", 55 | "cluster/manifests/example1/deployment.yaml": "example1-deployment-main", 56 | "cluster/manifests/example1/unknown.swp": "ignored", 57 | "cluster/manifests/example2/config.yaml": "example2-config-main", 58 | "cluster/manifests/example2/deployment.yaml": "example2-deployment-main", 59 | "cluster/manifests/deletions.yaml": "deletions", 60 | "cluster/node-pools/example/main.yaml": "node-pool", 61 | "cluster/etcd/stack.yaml": "etcd", 62 | "cluster/config-defaults.yaml": "defaults", 63 | "cluster/stack.yaml": "stack", 64 | }) 65 | 66 | SetupConfig( 67 | t, secondaryDir, 68 | map[string]string{ 69 | "cluster/manifests/example1/deployment.yaml": "example1-deployment-secondary", 70 | "cluster/manifests/example3/deployment.yaml": "example3-deployment-secondary", 71 | "cluster/manifests/deletions.yaml": "secondary-deletions", 72 | "cluster/node-pools/example/deployment.yaml": "secondary-node-pool", 73 | "cluster/config-defaults.yaml": "secondary-defaults", 74 | "cluster/stack.yaml": "secondary-stack", 75 | }) 76 | 77 | combined, err := NewCombinedSource([]ConfigSource{mainSrc, secondarySrc}) 78 | require.NoError(t, err) 79 | 80 | anyVersion, err := combined.Version("foobar", nil) 81 | require.NoError(t, err) 82 | 83 | config, err := anyVersion.Get(context.Background(), logger) 84 | require.NoError(t, err) 85 | 86 | stack, err := config.StackManifest("stack.yaml") 87 | require.NoError(t, err) 88 | require.Equal(t, expectedManifest("main", "cluster/stack.yaml", "stack"), stack) 89 | 90 | etcdStack, err := config.EtcdManifest("stack.yaml") 91 | require.NoError(t, err) 92 | require.Equal(t, expectedManifest("main", "cluster/etcd/stack.yaml", "etcd"), etcdStack) 93 | 94 | pool, err := config.NodePoolManifest("example", "main.yaml") 95 | require.NoError(t, err) 96 | require.Equal(t, expectedManifest("main", "cluster/node-pools/example/main.yaml", "node-pool"), pool) 97 | 98 | defaults, err := config.DefaultsManifests() 99 | require.NoError(t, err) 100 | require.Equal(t, []Manifest{ 101 | expectedManifest("main", "cluster/config-defaults.yaml", "defaults"), 102 | expectedManifest("secondary", "cluster/config-defaults.yaml", "secondary-defaults"), 103 | }, defaults) 104 | 105 | deletions, err := config.DeletionsManifests() 106 | require.NoError(t, err) 107 | require.Equal(t, []Manifest{ 108 | expectedManifest("main", "cluster/manifests/deletions.yaml", "deletions"), 109 | expectedManifest("secondary", "cluster/manifests/deletions.yaml", "secondary-deletions"), 110 | }, deletions) 111 | 112 | manifests, err := config.Components() 113 | require.NoError(t, err) 114 | expected := []Component{ 115 | // From main 116 | { 117 | Name: "main/example1", 118 | Manifests: []Manifest{ 119 | expectedManifest("main", "cluster/manifests/example1/config.yaml", "example1-config-main"), 120 | expectedManifest("main", "cluster/manifests/example1/deployment.yaml", "example1-deployment-main"), 121 | }, 122 | }, 123 | // From main 124 | { 125 | Name: "main/example2", 126 | Manifests: []Manifest{ 127 | expectedManifest("main", "cluster/manifests/example2/config.yaml", "example2-config-main"), 128 | expectedManifest("main", "cluster/manifests/example2/deployment.yaml", "example2-deployment-main"), 129 | }, 130 | }, 131 | // From secondary, same name as in main 132 | { 133 | Name: "secondary/example1", 134 | Manifests: []Manifest{ 135 | expectedManifest("secondary", "cluster/manifests/example1/deployment.yaml", "example1-deployment-secondary"), 136 | }, 137 | }, 138 | // From secondary 139 | { 140 | Name: "secondary/example3", 141 | Manifests: []Manifest{ 142 | expectedManifest("secondary", "cluster/manifests/example3/deployment.yaml", "example3-deployment-secondary"), 143 | }, 144 | }, 145 | } 146 | 147 | require.Equal(t, expected, manifests) 148 | 149 | require.NoError(t, config.Delete()) 150 | } 151 | -------------------------------------------------------------------------------- /channel/config_source.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | type ConfigVersion interface { 10 | // ID returns this version's unique ID (e.g. a git commit SHA) 11 | ID() string 12 | 13 | // Get returns a Config related to the specified version from the local copy. 14 | Get(ctx context.Context, logger *log.Entry) (Config, error) 15 | } 16 | 17 | // ConfigSource is an interface for getting the cluster configuration for a 18 | // certain channel. 19 | type ConfigSource interface { 20 | // Name of the config source 21 | Name() string 22 | 23 | // Update synchronizes the local copy of the configuration with the remote one. 24 | Update(ctx context.Context, logger *log.Entry) error 25 | 26 | // Version returns the ConfigVersion for the corresponding channel (or overrides, if this is a 27 | // combined source) 28 | Version(channel string, overrides map[string]string) (ConfigVersion, error) 29 | } 30 | 31 | type Manifest struct { 32 | Path string 33 | Contents []byte 34 | } 35 | 36 | type Component struct { 37 | Name string 38 | Manifests []Manifest 39 | } 40 | 41 | type Config interface { 42 | StackManifest(manifestName string) (Manifest, error) 43 | EtcdManifest(manifestName string) (Manifest, error) 44 | NodePoolManifest(profileName string, manifestName string) (Manifest, error) 45 | DefaultsManifests() ([]Manifest, error) 46 | DeletionsManifests() ([]Manifest, error) 47 | Components() ([]Component, error) 48 | Delete() error 49 | } 50 | -------------------------------------------------------------------------------- /channel/directory.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // Directory defines a channel source where everything is stored in a directory. 10 | type Directory struct { 11 | name string 12 | location string 13 | } 14 | 15 | // NewDirectory initializes a new directory-based ChannelSource. 16 | func NewDirectory(name string, location string) (ConfigSource, error) { 17 | return &Directory{ 18 | name: name, 19 | location: location, 20 | }, nil 21 | } 22 | 23 | func (d *Directory) Name() string { 24 | return d.name 25 | } 26 | 27 | func (d *Directory) Update(_ context.Context, _ *log.Entry) error { 28 | return nil 29 | } 30 | 31 | func (d *Directory) Version(_ string, _ map[string]string) (ConfigVersion, error) { 32 | return d, nil 33 | } 34 | 35 | func (d *Directory) ID() string { 36 | return "" 37 | } 38 | 39 | func (d *Directory) Get(_ context.Context, _ *log.Entry) (Config, error) { 40 | return NewSimpleConfig(d.name, d.location, false) 41 | } 42 | -------------------------------------------------------------------------------- /channel/directory_test.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | log "github.com/sirupsen/logrus" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDirectoryChannel(t *testing.T) { 13 | logger := log.StandardLogger().WithFields(map[string]interface{}{}) 14 | 15 | tempDir := CreateTempDir(t) 16 | defer os.RemoveAll(tempDir) 17 | 18 | setupExampleConfig(t, tempDir, "main") 19 | 20 | d, err := NewDirectory("testsrc", tempDir) 21 | require.NoError(t, err) 22 | 23 | err = d.Update(context.Background(), logger) 24 | require.NoError(t, err) 25 | 26 | anyVersion, err := d.Version("foobar", nil) 27 | require.NoError(t, err) 28 | 29 | config, err := anyVersion.Get(context.Background(), logger) 30 | require.NoError(t, err) 31 | 32 | verifyExampleConfig(t, config, "testsrc", "main") 33 | 34 | require.NoError(t, config.Delete()) 35 | _, err = os.Stat(tempDir) 36 | require.NoError(t, err) 37 | } 38 | -------------------------------------------------------------------------------- /channel/git.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "path/filepath" 11 | "regexp" 12 | "strings" 13 | "sync" 14 | "sync/atomic" 15 | "time" 16 | 17 | log "github.com/sirupsen/logrus" 18 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/util/command" 19 | ) 20 | 21 | // Git defines a channel source where the channels are stored in a git 22 | // repository. 23 | type Git struct { 24 | name string 25 | exec *command.ExecManager 26 | workdir string 27 | repositoryURL string 28 | repoName string 29 | repoDir string 30 | sshPrivateKeyFile string 31 | mutex *sync.Mutex 32 | counter uint64 33 | } 34 | 35 | type gitVersion struct { 36 | git *Git 37 | sha string 38 | } 39 | 40 | func (v *gitVersion) ID() string { 41 | return v.sha 42 | } 43 | 44 | func (v *gitVersion) Get(ctx context.Context, logger *log.Entry) (Config, error) { 45 | repoDir, err := v.git.localClone(ctx, logger, v.sha) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return NewSimpleConfig(v.git.name, repoDir, true) 51 | 52 | } 53 | 54 | // NewGit initializes a new git based ChannelSource. 55 | func NewGit(execManager *command.ExecManager, name string, workdir, repositoryURL, sshPrivateKeyFile string) (ConfigSource, error) { 56 | absWorkdir, err := filepath.Abs(workdir) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // get repo name from repo URL. 62 | repoName, err := getRepoName(repositoryURL) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return &Git{ 68 | name: name, 69 | exec: execManager, 70 | workdir: absWorkdir, 71 | repoName: repoName, 72 | repositoryURL: repositoryURL, 73 | repoDir: path.Join(absWorkdir, repoName), 74 | sshPrivateKeyFile: sshPrivateKeyFile, 75 | mutex: &sync.Mutex{}, 76 | }, nil 77 | } 78 | 79 | var repoNameRE = regexp.MustCompile(`/?([\w-]+)(.git)?$`) 80 | 81 | // getRepoName parses the repository name given a repository URI. 82 | func getRepoName(repoURI string) (string, error) { 83 | match := repoNameRE.FindStringSubmatch(repoURI) 84 | if len(match) != 3 { 85 | return "", fmt.Errorf("could not parse repository name from uri: %s", repoURI) 86 | } 87 | return match[1], nil 88 | } 89 | 90 | func (g *Git) Name() string { 91 | return g.name 92 | } 93 | 94 | func (g *Git) Update(ctx context.Context, logger *log.Entry) error { 95 | g.mutex.Lock() 96 | defer g.mutex.Unlock() 97 | 98 | _, err := os.Stat(g.repoDir) 99 | if err != nil { 100 | if !os.IsNotExist(err) { 101 | return err 102 | } 103 | 104 | err = g.cmd(ctx, logger, "clone", "--mirror", g.repositoryURL, g.repoDir) 105 | if err != nil { 106 | return err 107 | } 108 | } 109 | 110 | err = g.cmd(ctx, logger, "--git-dir", g.repoDir, "remote", "update", "--prune") 111 | if err != nil { 112 | return err 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func (g *Git) Version(channel string, _ map[string]string) (ConfigVersion, error) { 119 | stderr := new(bytes.Buffer) 120 | cmd := exec.Command("git", "--git-dir", g.repoDir, "rev-parse", channel) 121 | cmd.Stderr = stderr 122 | sha, err := cmd.Output() 123 | if err != nil { 124 | return nil, fmt.Errorf("failed to execute git: %s: %w", stderr.String(), err) 125 | } 126 | 127 | return &gitVersion{ 128 | git: g, 129 | sha: strings.TrimSpace(string(sha)), 130 | }, nil 131 | } 132 | 133 | // localClone duplicates a repo by cloning to temp location with unix time 134 | // suffix this will be the path that is exposed through the Config. This 135 | // makes sure that each caller (possibly running concurrently) get it's 136 | // own version of the checkout, such that they can run concurrently 137 | // without data races. 138 | func (g *Git) localClone(ctx context.Context, logger *log.Entry, channel string) (string, error) { 139 | i := atomic.AddUint64(&g.counter, 1) 140 | repoDir := path.Join(g.workdir, fmt.Sprintf("%s_%s_%d_%d", g.repoName, channel, time.Now().UTC().UnixNano(), i)) 141 | 142 | srcRepoURL := fmt.Sprintf("file://%s", g.repoDir) 143 | err := g.cmd(ctx, logger, "clone", srcRepoURL, repoDir) 144 | if err != nil { 145 | return "", err 146 | } 147 | 148 | err = g.cmd(ctx, logger, "-C", repoDir, "checkout", channel) 149 | if err != nil { 150 | return "", err 151 | } 152 | 153 | return repoDir, nil 154 | } 155 | 156 | // cmd executes a git command with the correct environment set. 157 | func (g *Git) cmd(ctx context.Context, logger *log.Entry, args ...string) error { 158 | cmd := exec.Command("git", args...) 159 | // set GIT_SSH_COMMAND with private-key file when pulling over ssh. 160 | if g.sshPrivateKeyFile != "" { 161 | cmd.Env = []string{fmt.Sprintf("GIT_SSH_COMMAND=ssh -i %s -o 'StrictHostKeyChecking no'", g.sshPrivateKeyFile)} 162 | } 163 | 164 | _, err := g.exec.RunSilently(ctx, logger, cmd) 165 | return err 166 | } 167 | -------------------------------------------------------------------------------- /channel/git_test.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "testing" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/stretchr/testify/require" 12 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/util/command" 13 | ) 14 | 15 | // helper function to setup a test repository. 16 | func createGitRepo(t *testing.T, logger *log.Entry, dir string) { 17 | err := exec.Command("git", "-C", dir, "init", "-b", "main").Run() 18 | require.NoError(t, err) 19 | 20 | execManager := command.NewExecManager(1) 21 | 22 | commit := func(message string) { 23 | cmd := exec.Command("git", "-C", dir, "commit", "-am", message) 24 | cmd.Env = []string{ 25 | "GIT_AUTHOR_EMAIL=go-test", 26 | "GIT_AUTHOR_NAME=go-test", 27 | "GIT_COMMITTER_EMAIL=go-test", 28 | "GIT_COMMITTER_NAME=go-test", 29 | } 30 | log.Printf("test") 31 | _, err := execManager.RunSilently(context.Background(), logger, cmd) 32 | require.NoError(t, err) 33 | } 34 | 35 | setupExampleConfig(t, dir, "channel1") 36 | 37 | err = exec.Command("git", "-C", dir, "add", "cluster").Run() 38 | require.NoError(t, err) 39 | 40 | commit("initial commit") 41 | 42 | err = exec.Command("git", "-C", dir, "checkout", "-b", "channel2").Run() 43 | require.NoError(t, err) 44 | 45 | setupExampleConfig(t, dir, "channel2") 46 | 47 | err = exec.Command("git", "-C", dir, "add", "cluster").Run() 48 | require.NoError(t, err) 49 | 50 | commit("branch commit") 51 | } 52 | 53 | func checkout(t *testing.T, logger *log.Entry, source ConfigSource, channel string) Config { 54 | version, err := source.Version(channel, nil) 55 | require.NoError(t, err) 56 | 57 | checkout, err := version.Get(context.Background(), logger) 58 | require.NoError(t, err) 59 | 60 | return checkout 61 | } 62 | 63 | func TestGitGet(t *testing.T) { 64 | logger := log.WithFields(map[string]interface{}{}) 65 | 66 | repoTempDir := CreateTempDir(t) 67 | defer os.RemoveAll(repoTempDir) 68 | 69 | workDir := CreateTempDir(t) 70 | defer os.RemoveAll(workDir) 71 | 72 | createGitRepo(t, logger, repoTempDir) 73 | c, err := NewGit(command.NewExecManager(1), "testsrc", workDir, repoTempDir, "") 74 | require.NoError(t, err) 75 | 76 | err = c.Update(context.Background(), logger) 77 | require.NoError(t, err) 78 | 79 | // check main channel 80 | main := checkout(t, logger, c, "main") 81 | verifyExampleConfig(t, main, "testsrc", "channel1") 82 | 83 | // check another channel 84 | channel2 := checkout(t, logger, c, "channel2") 85 | verifyExampleConfig(t, channel2, "testsrc", "channel2") 86 | 87 | // check sha 88 | out, err := exec.Command( 89 | "git", 90 | "-C", 91 | repoTempDir, 92 | "rev-parse", 93 | "main", 94 | ).Output() 95 | require.NoError(t, err) 96 | 97 | sha := checkout(t, logger, c, strings.TrimSpace(string(out))) 98 | verifyExampleConfig(t, sha, "testsrc", "channel1") 99 | } 100 | 101 | func TestGetRepoName(t *testing.T) { 102 | for _, tc := range []struct { 103 | msg string 104 | name string 105 | uri string 106 | success bool 107 | }{ 108 | { 109 | msg: "get reponame from github URL", 110 | name: "kubernetes-on-aws", 111 | uri: "https://github.com/zalando-incubator/kubernetes-on-aws.git", 112 | success: true, 113 | }, 114 | { 115 | msg: "get reponame from full local path with .git suffix", 116 | name: "kubernetes-on-aws", 117 | uri: "/kubernetes-on-aws.git", 118 | success: true, 119 | }, 120 | { 121 | msg: "get reponame from relative local path with .git suffix", 122 | name: "kubernetes-on-aws", 123 | uri: "kubernetes-on-aws.git", 124 | success: true, 125 | }, 126 | { 127 | msg: "get reponame from dot relative local path with .git suffix", 128 | name: "kubernetes-on-aws", 129 | uri: "./kubernetes-on-aws.git", 130 | success: true, 131 | }, 132 | { 133 | msg: "get reponame from full local path without .git suffix", 134 | name: "kubernetes-on-aws", 135 | uri: "/kubernetes-on-aws", 136 | success: true, 137 | }, 138 | { 139 | msg: "get reponame from relative local path without .git suffix", 140 | name: "kubernetes-on-aws", 141 | uri: "kubernetes-on-aws", 142 | success: true, 143 | }, 144 | { 145 | msg: "get reponame from dot relative local path without .git suffix", 146 | name: "kubernetes-on-aws", 147 | uri: "./kubernetes-on-aws", 148 | success: true, 149 | }, 150 | { 151 | msg: "empty relative path should be invalid", 152 | name: "", 153 | uri: "./", 154 | success: false, 155 | }, 156 | { 157 | msg: "empty full path should be invalid", 158 | name: "", 159 | uri: "/", 160 | success: false, 161 | }, 162 | { 163 | msg: "empty uri should be invalid", 164 | name: "", 165 | uri: "", 166 | success: false, 167 | }, 168 | } { 169 | t.Run(tc.msg, func(t *testing.T) { 170 | name, err := getRepoName(tc.uri) 171 | if err != nil && tc.success { 172 | t.Errorf("should not fail: %s", err) 173 | } 174 | 175 | if name != tc.name && tc.success { 176 | t.Errorf("expected repo name %s, got %s", tc.name, name) 177 | } 178 | }) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /channel/mock_source.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type mockSource struct { 11 | name string 12 | validChannels []string 13 | } 14 | 15 | type mockVersion struct { 16 | channel string 17 | } 18 | 19 | func (s *mockSource) Name() string { 20 | return s.name 21 | } 22 | 23 | func (s *mockSource) Update(_ context.Context, _ *logrus.Entry) error { 24 | return nil 25 | } 26 | 27 | func (s *mockSource) Version(channel string, _ map[string]string) (ConfigVersion, error) { 28 | for _, validChannel := range s.validChannels { 29 | if validChannel == channel { 30 | return &mockVersion{channel: channel}, nil 31 | } 32 | } 33 | return nil, fmt.Errorf("unknown version: %s", channel) 34 | } 35 | 36 | func (v *mockVersion) ID() string { 37 | return v.channel 38 | } 39 | 40 | func (v *mockVersion) Get(_ context.Context, _ *logrus.Entry) (Config, error) { 41 | return nil, fmt.Errorf("not implemented") 42 | } 43 | -------------------------------------------------------------------------------- /channel/simple_config.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | configRoot = "cluster" 12 | poolConfigDir = "node-pools" 13 | etcdConfigDir = "etcd" 14 | defaultsFile = "config-defaults.yaml" 15 | manifestsDir = "manifests" 16 | deletionsFile = "deletions.yaml" 17 | ) 18 | 19 | type SimpleConfig struct { 20 | pathPrefix string 21 | baseDir string 22 | allowDelete bool 23 | } 24 | 25 | func NewSimpleConfig(sourceName string, baseDir string, allowDelete bool) (*SimpleConfig, error) { 26 | abspath, err := filepath.Abs(path.Clean(baseDir)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return &SimpleConfig{ 31 | pathPrefix: sourceName + "/", 32 | baseDir: abspath, 33 | allowDelete: allowDelete, 34 | }, nil 35 | } 36 | 37 | func (c *SimpleConfig) readManifest(manifestDirectory string, name string) (Manifest, error) { 38 | filePath := path.Join(c.baseDir, manifestDirectory, name) 39 | res, err := os.ReadFile(filePath) 40 | if err != nil { 41 | return Manifest{}, err 42 | } 43 | return Manifest{ 44 | Path: path.Join(c.pathPrefix, manifestDirectory, name), 45 | Contents: res, 46 | }, nil 47 | } 48 | 49 | func (c *SimpleConfig) StackManifest(manifestName string) (Manifest, error) { 50 | return c.readManifest(configRoot, manifestName) 51 | } 52 | 53 | func (c *SimpleConfig) EtcdManifest(manifestName string) (Manifest, error) { 54 | return c.readManifest(path.Join(configRoot, etcdConfigDir), manifestName) 55 | } 56 | 57 | func (c *SimpleConfig) NodePoolManifest(profileName string, manifestName string) (Manifest, error) { 58 | return c.readManifest(path.Join(configRoot, poolConfigDir, profileName), manifestName) 59 | } 60 | 61 | func (c *SimpleConfig) DefaultsManifests() ([]Manifest, error) { 62 | res, err := c.readManifest(configRoot, defaultsFile) 63 | if err != nil { 64 | if os.IsNotExist(err) { 65 | return nil, nil 66 | } 67 | return nil, err 68 | } 69 | return []Manifest{res}, nil 70 | } 71 | 72 | func (c *SimpleConfig) DeletionsManifests() ([]Manifest, error) { 73 | res, err := c.readManifest(path.Join(configRoot, manifestsDir), deletionsFile) 74 | if err != nil { 75 | if os.IsNotExist(err) { 76 | return nil, nil 77 | } 78 | return nil, err 79 | } 80 | return []Manifest{res}, nil 81 | } 82 | 83 | func (c *SimpleConfig) Components() ([]Component, error) { 84 | var result []Component 85 | 86 | componentsDir := path.Join(c.baseDir, configRoot, manifestsDir) 87 | components, err := os.ReadDir(componentsDir) 88 | if err != nil { 89 | if os.IsNotExist(err) { 90 | return nil, nil 91 | } 92 | return nil, err 93 | } 94 | for _, component := range components { 95 | if !component.IsDir() { 96 | continue 97 | } 98 | 99 | var manifests []Manifest 100 | 101 | componentDir := path.Join(componentsDir, component.Name()) 102 | files, err := os.ReadDir(componentDir) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | for _, file := range files { 108 | if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") { 109 | continue 110 | } 111 | manifest, err := c.readManifest(path.Join(configRoot, manifestsDir, component.Name()), file.Name()) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | manifests = append(manifests, manifest) 117 | } 118 | result = append(result, Component{ 119 | Name: component.Name(), 120 | Manifests: manifests, 121 | }) 122 | } 123 | return result, nil 124 | } 125 | 126 | func (c *SimpleConfig) Delete() error { 127 | if !c.allowDelete { 128 | return nil 129 | } 130 | return os.RemoveAll(c.baseDir) 131 | } 132 | -------------------------------------------------------------------------------- /channel/test_util.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func CreateTempDir(t *testing.T) string { 12 | res, err := os.MkdirTemp("", t.Name()) 13 | require.NoError(t, err) 14 | return res 15 | } 16 | 17 | func setupExampleConfig(t *testing.T, baseDir string, mainStack string) { 18 | SetupConfig( 19 | t, baseDir, 20 | map[string]string{ 21 | "cluster/manifests/example1/main.yaml": "example1-main", 22 | "cluster/manifests/example1/unknown.swp": "ignored", 23 | "cluster/manifests/example2/config.yaml": "example2-config", 24 | "cluster/manifests/example2/main.yaml": "example2-main", 25 | "cluster/manifests/deletions.yaml": "deletions", 26 | "cluster/node-pools/example/main.yaml": "node-pool", 27 | "cluster/config-defaults.yaml": "defaults", 28 | "cluster/etcd/files.yaml": "etcd-files", 29 | "cluster/stack.yaml": mainStack, 30 | }) 31 | } 32 | 33 | func SetupConfig(t *testing.T, baseDir string, manifests map[string]string) { 34 | for manifestPath, contents := range manifests { 35 | fullpath := path.Join(baseDir, manifestPath) 36 | 37 | err := os.MkdirAll(path.Dir(fullpath), 0755) 38 | require.NoError(t, err) 39 | 40 | err = os.WriteFile(fullpath, []byte(contents), 0644) 41 | require.NoError(t, err) 42 | } 43 | } 44 | 45 | func expectedManifest(sourceName, manifestPath string, contents string) Manifest { 46 | return Manifest{ 47 | Path: path.Join(sourceName, manifestPath), 48 | Contents: []byte(contents), 49 | } 50 | } 51 | 52 | func verifyExampleConfig(t *testing.T, config Config, sourceName string, mainStack string) { 53 | stack, err := config.StackManifest("stack.yaml") 54 | require.NoError(t, err) 55 | require.Equal(t, expectedManifest(sourceName, "cluster/stack.yaml", mainStack), stack) 56 | 57 | pool, err := config.NodePoolManifest("example", "main.yaml") 58 | require.NoError(t, err) 59 | require.Equal(t, expectedManifest(sourceName, "cluster/node-pools/example/main.yaml", "node-pool"), pool) 60 | 61 | defaults, err := config.DefaultsManifests() 62 | require.NoError(t, err) 63 | require.Equal(t, []Manifest{expectedManifest(sourceName, "cluster/config-defaults.yaml", "defaults")}, defaults) 64 | 65 | deletions, err := config.DeletionsManifests() 66 | require.NoError(t, err) 67 | require.Equal(t, []Manifest{expectedManifest(sourceName, "cluster/manifests/deletions.yaml", "deletions")}, deletions) 68 | 69 | manifests, err := config.Components() 70 | require.NoError(t, err) 71 | expected := []Component{ 72 | { 73 | Name: "example1", 74 | Manifests: []Manifest{ 75 | expectedManifest(sourceName, "cluster/manifests/example1/main.yaml", "example1-main"), 76 | }, 77 | }, 78 | { 79 | Name: "example2", 80 | Manifests: []Manifest{ 81 | expectedManifest(sourceName, "cluster/manifests/example2/config.yaml", "example2-config"), 82 | expectedManifest(sourceName, "cluster/manifests/example2/main.yaml", "example2-main"), 83 | }, 84 | }, 85 | } 86 | require.Equal(t, expected, manifests) 87 | } 88 | -------------------------------------------------------------------------------- /config/include_exclude.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "regexp" 4 | 5 | const DefaultInclude = ".*" 6 | const DefaultExclude = "^$" 7 | 8 | var DefaultFilter = IncludeExcludeFilter{ 9 | Include: regexp.MustCompile(DefaultInclude), 10 | Exclude: regexp.MustCompile(DefaultExclude), 11 | } 12 | 13 | // IncludeExcludeFilter is a filter consiting of two regular 14 | // expressions. It can be used to decide if a certain item is allowed 15 | // based on the filters. 16 | type IncludeExcludeFilter struct { 17 | Include *regexp.Regexp 18 | Exclude *regexp.Regexp 19 | } 20 | 21 | // Allowed returns true if the item is allowed based on the 22 | // IncludeExcludeFilter object. Exclude will trump the include. 23 | func (f *IncludeExcludeFilter) Allowed(item string) bool { 24 | return !f.Exclude.MatchString(item) && f.Include.MatchString(item) 25 | } 26 | -------------------------------------------------------------------------------- /config/include_exclude_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | ) 7 | 8 | func TestIncludeExcludeFilter(t *testing.T) { 9 | for _, ti := range []struct { 10 | msg string 11 | filter IncludeExcludeFilter 12 | item string 13 | allowed bool 14 | }{ 15 | { 16 | msg: "Default filter grants access to any item", 17 | filter: IncludeExcludeFilter{ 18 | Include: regexp.MustCompile(DefaultInclude), 19 | Exclude: regexp.MustCompile(DefaultExclude), 20 | }, 21 | item: "a", 22 | allowed: true, 23 | }, 24 | { 25 | msg: "Prefix include filter grants access all items with the prefix", 26 | filter: IncludeExcludeFilter{ 27 | Include: regexp.MustCompile("gke:*"), 28 | Exclude: regexp.MustCompile(DefaultExclude), 29 | }, 30 | item: "gke:123", 31 | allowed: true, 32 | }, 33 | { 34 | msg: "Prefix include filter does not grant access to items without the prefix", 35 | filter: IncludeExcludeFilter{ 36 | Include: regexp.MustCompile("gke:*"), 37 | Exclude: regexp.MustCompile(DefaultExclude), 38 | }, 39 | item: "aws:123", 40 | allowed: false, 41 | }, 42 | { 43 | msg: "Exclude has precedence over Include", 44 | filter: IncludeExcludeFilter{ 45 | Include: regexp.MustCompile("gke:*"), 46 | Exclude: regexp.MustCompile("gke:*"), 47 | }, 48 | item: "gke:123", 49 | allowed: false, 50 | }, 51 | } { 52 | t.Run(ti.msg, func(t *testing.T) { 53 | if ti.allowed != ti.filter.Allowed(ti.item) { 54 | t.Errorf("Expected %s to be allowed: %t, got allowed %t", ti.item, ti.allowed, ti.filter.Allowed(ti.item)) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /delivery.yaml: -------------------------------------------------------------------------------- 1 | version: "2017-09-20" 2 | pipeline: 3 | - id: build 4 | vm_config: 5 | type: linux 6 | image: "cdp-runtime/go" 7 | size: large 8 | type: script 9 | env: 10 | GOFLAGS: "-mod=readonly" 11 | cache: 12 | paths: 13 | - /go/pkg/mod 14 | - ~/.cache/go-build 15 | commands: 16 | - desc: test & check 17 | cmd: | 18 | make lint 19 | make test 20 | - desc: Build and push image to Zalando's registry 21 | cmd: | 22 | if [[ $CDP_TARGET_BRANCH == master && ! $CDP_PULL_REQUEST_NUMBER ]]; then 23 | VERSION=$(git describe --tags --always --dirty) 24 | else 25 | VERSION=$CDP_BUILD_VERSION 26 | fi 27 | 28 | IMAGE=container-registry-test.zalando.net/teapot/cluster-lifecycle-manager 29 | make build.linux.amd64 build.linux.arm64 30 | 31 | docker buildx create --config /etc/cdp-buildkitd.toml --driver-opt network=host --bootstrap --use 32 | docker buildx build --rm --build-arg BASE_IMAGE=container-registry.zalando.net/library/amazonlinux-2023-slim:latest -t "${IMAGE}:${VERSION}" --platform linux/amd64,linux/arm64 --push . 33 | cdp-promote-image "${IMAGE}:${VERSION}" 34 | -------------------------------------------------------------------------------- /docs/proposals/adr-002-apply-kube-system.md: -------------------------------------------------------------------------------- 1 | # ADR-002: Installation of Kubernetes non core system components 2 | 3 | ## Context 4 | 5 | In `cluster.py` we used to install all the `kube-system` components using a `systemd` unit. This consisted basically in a bash script that deployed all the manifests from `/srv/kubernetes/manifests/*/*.yaml` using `kubectl`. 6 | We obviously do not want to update versions manually via kubectl. Furthermore, this approach also meant that we had to launch a new master instance in order to apply the updated manifests. 7 | 8 | ## Decision 9 | We will do the following: 10 | 11 | - remove entirely the "install-kube-system" unit from the master user data. 12 | - create a folder with all the manifests for each of the kubernetes artifact 13 | - apply all the manifests from the Cluster Lifecycle Manager code 14 | 15 | Some of the possible alternatives for the folder structures are: 16 | 17 | 1. /manifests/APPLICATION_NAME/deployment.yaml - which uses a folder structure that includes the APPLICATION_NAME 18 | 19 | 2. /manifests/APPLICATION_NAME/KIND/mate.yaml - which uses a folder structure that includes APPLICATION_NAME and KIND 20 | 21 | 3. /manifests/mate-deployment.yaml - where we have a flat structure and the filenames contain the name of the application and the kind 22 | 23 | 4. /manifests/mate.yaml - where mate.yaml contains all the artifacts of all kinds related to mate 24 | 25 | We choose number 1 as it seems the most compelling alternative. 26 | Number 2 will only introduce an additional folder level that does not provide any benefit. Number 3 will instead rely on a naming convention on the given kind. 27 | Number 4, instead, is a competitive alternative to number 1 and could be adopted, but we prefer to go with number 1 as this is very flexible and probably more readable for the maintainer. 28 | For the file naming convention, we recommend to split in files for kind when is possible and put the name (or just a prefix) in the file name. We will not make any assumption on the file naming scheme in the code. 29 | Also, no assumption will be made on the order of execution of such files. 30 | 31 | ## Status 32 | 33 | Accepted. 34 | 35 | ## Consequences 36 | 37 | The chosen file convention will be relevant when discussing the removal of components from `kube-system`. 38 | This is currently out of scope for this ADR as this only covers the "apply" case. 39 | -------------------------------------------------------------------------------- /pkg/aws/assume_role_provider.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "time" 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/session" 9 | "github.com/aws/aws-sdk-go/service/sts" 10 | ) 11 | 12 | // AssumeRoleProvider is an AWS SDK credentials provider which retrieve 13 | // credentials by assuming the defined role. 14 | type AssumeRoleProvider struct { 15 | role string 16 | sessionName string 17 | creds *sts.Credentials 18 | sts *sts.STS 19 | } 20 | 21 | // NewAssumeRoleProvider initializes a new AssumeRoleProvider. 22 | func NewAssumeRoleProvider(role, sessionName string, sess *session.Session) *AssumeRoleProvider { 23 | return &AssumeRoleProvider{ 24 | role: role, 25 | sessionName: sessionName, 26 | sts: sts.New(sess), 27 | } 28 | } 29 | 30 | // Retrieve retrieves new credentials by assuming the defined role. 31 | func (a *AssumeRoleProvider) Retrieve() (credentials.Value, error) { 32 | params := &sts.AssumeRoleInput{ 33 | RoleArn: aws.String(a.role), 34 | RoleSessionName: aws.String(a.sessionName), 35 | } 36 | 37 | resp, err := a.sts.AssumeRole(params) 38 | if err != nil { 39 | return credentials.Value{}, err 40 | } 41 | 42 | a.creds = resp.Credentials 43 | 44 | return credentials.Value{ 45 | AccessKeyID: *resp.Credentials.AccessKeyId, 46 | SecretAccessKey: *resp.Credentials.SecretAccessKey, 47 | SessionToken: *resp.Credentials.SessionToken, 48 | ProviderName: "assumeRoleProvider", 49 | }, nil 50 | } 51 | 52 | // IsExpired is true when the credentials has expired and must be retrieved 53 | // again. 54 | func (a *AssumeRoleProvider) IsExpired() bool { 55 | if a.creds != nil && a.creds.Expiration != nil { 56 | return time.Now().UTC().After(*a.creds.Expiration) 57 | } 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /pkg/aws/eks/token.go: -------------------------------------------------------------------------------- 1 | package eks 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | "github.com/aws/aws-sdk-go/service/sts" 6 | "golang.org/x/oauth2" 7 | awsiamtoken "sigs.k8s.io/aws-iam-authenticator/pkg/token" 8 | ) 9 | 10 | func NewTokenSource(sess *session.Session, clusterName string) *TokenSource { 11 | return &TokenSource{ 12 | session: sess, 13 | clusterName: clusterName, 14 | } 15 | } 16 | 17 | type TokenSource struct { 18 | session *session.Session 19 | clusterName string 20 | } 21 | 22 | func (ts *TokenSource) Token() (*oauth2.Token, error) { 23 | // TODO: cached? 24 | gen, err := awsiamtoken.NewGenerator(true, false) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | stsAPI := sts.New(ts.session) 30 | 31 | awsToken, err := gen.GetWithSTS(ts.clusterName, stsAPI) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &oauth2.Token{ 36 | AccessToken: awsToken.Token, 37 | Expiry: awsToken.Expiration, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/aws/retryer.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aws/aws-sdk-go/aws/client" 7 | "github.com/aws/aws-sdk-go/aws/request" 8 | ) 9 | 10 | const ( 11 | metadataService = "ec2metadata" 12 | maxMetadataServiceRetries = 8 13 | ) 14 | 15 | type clampedRetryer struct { 16 | client.DefaultRetryer 17 | maxRetryInterval time.Duration 18 | } 19 | 20 | func NewClampedRetryer(maxRetries int, maxRetryInterval time.Duration) request.Retryer { 21 | return clampedRetryer{ 22 | DefaultRetryer: client.DefaultRetryer{NumMaxRetries: maxRetries}, 23 | maxRetryInterval: maxRetryInterval, 24 | } 25 | } 26 | 27 | func (retryer clampedRetryer) ShouldRetry(r *request.Request) bool { 28 | if r.ClientInfo.ServiceName == metadataService { 29 | return r.RetryCount < maxMetadataServiceRetries 30 | } 31 | return retryer.DefaultRetryer.ShouldRetry(r) 32 | } 33 | 34 | func (retryer clampedRetryer) RetryRules(r *request.Request) time.Duration { 35 | baseInterval := retryer.DefaultRetryer.RetryRules(r) 36 | if baseInterval < retryer.maxRetryInterval { 37 | return baseInterval 38 | } 39 | return retryer.maxRetryInterval 40 | } 41 | -------------------------------------------------------------------------------- /pkg/aws/retryer_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go/aws/client/metadata" 8 | "github.com/aws/aws-sdk-go/aws/request" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestShouldRetry(t *testing.T) { 13 | for _, tc := range []struct { 14 | caseName string 15 | maxRetries int 16 | maxRetryInterval time.Duration 17 | request *request.Request 18 | expected bool 19 | }{ 20 | { 21 | caseName: "should not retry", 22 | maxRetries: 1, 23 | request: &request.Request{}, 24 | expected: false, 25 | }, 26 | { 27 | caseName: "should retry with metadata service", 28 | maxRetries: 1, 29 | request: &request.Request{ 30 | ClientInfo: metadata.ClientInfo{ 31 | ServiceName: "ec2metadata", 32 | }, 33 | }, 34 | expected: true, 35 | }, 36 | { 37 | caseName: "should not retry with metadata service", 38 | maxRetries: 1, 39 | request: &request.Request{ 40 | ClientInfo: metadata.ClientInfo{ 41 | ServiceName: "ec2metadata", 42 | }, 43 | RetryCount: 8, 44 | }, 45 | expected: false, 46 | }, 47 | } { 48 | t.Run(tc.caseName, func(t *testing.T) { 49 | retryer := NewClampedRetryer(tc.maxRetries, time.Second) 50 | 51 | res := retryer.ShouldRetry(tc.request) 52 | require.Equal(t, tc.expected, res) 53 | }) 54 | } 55 | } 56 | 57 | func TestRetryRules(t *testing.T) { 58 | for _, tc := range []struct { 59 | caseName string 60 | maxRetryInterval time.Duration 61 | request *request.Request 62 | expectedLessOrEqual time.Duration 63 | }{ 64 | { 65 | caseName: "should return max retry interval", 66 | maxRetryInterval: time.Millisecond, 67 | request: &request.Request{}, 68 | expectedLessOrEqual: time.Millisecond, 69 | }, 70 | { 71 | caseName: "should not return max retry interval", 72 | maxRetryInterval: time.Second, 73 | request: &request.Request{ 74 | ClientInfo: metadata.ClientInfo{ 75 | ServiceName: "ec2metadata", 76 | }, 77 | }, 78 | expectedLessOrEqual: time.Second / 2, 79 | }, 80 | } { 81 | t.Run(tc.caseName, func(t *testing.T) { 82 | retryer := NewClampedRetryer(1, tc.maxRetryInterval) 83 | 84 | res := retryer.RetryRules(tc.request) 85 | require.LessOrEqual(t, res, tc.expectedLessOrEqual) 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/aws/session.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "time" 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/ec2metadata" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | ) 11 | 12 | const ( 13 | awsSessionName = "cluster-lifecycle-manager" 14 | ) 15 | 16 | // Config sets up configuration for AWS session 17 | func Config(maxRetries int, maxRetryInterval time.Duration) *aws.Config { 18 | result := aws.NewConfig() 19 | result.Retryer = NewClampedRetryer(maxRetries, maxRetryInterval) 20 | result.EnforceShouldRetryCheck = aws.Bool(true) 21 | return result 22 | } 23 | 24 | // Session sets up an AWS session with the region automatically detected from 25 | // the environment or the ec2 metadata service if running on ec2. 26 | func Session(config *aws.Config, assumedRole string) (*session.Session, error) { 27 | sess, err := session.NewSessionWithOptions(session.Options{ 28 | Config: *config, 29 | SharedConfigState: session.SharedConfigEnable, 30 | }) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | if aws.StringValue(sess.Config.Region) == "" { 36 | // try to get region from metadata service 37 | metadata := ec2metadata.New(sess) 38 | region, err := metadata.Region() 39 | if err != nil { 40 | return nil, err 41 | } 42 | sess.Config.Region = aws.String(region) 43 | } 44 | 45 | if assumedRole != "" { 46 | sess.Config.WithCredentials(credentials.NewCredentials(NewAssumeRoleProvider(assumedRole, awsSessionName, sess))) 47 | } 48 | 49 | return sess, nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/cluster_registry_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package client 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "github.com/go-openapi/runtime" 10 | httptransport "github.com/go-openapi/runtime/client" 11 | "github.com/go-openapi/strfmt" 12 | 13 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/client/clusters" 14 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/client/config_items" 15 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/client/infrastructure_accounts" 16 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/client/node_pool_config_items" 17 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/client/node_pools" 18 | ) 19 | 20 | // Default cluster registry HTTP client. 21 | var Default = NewHTTPClient(nil) 22 | 23 | const ( 24 | // DefaultHost is the default Host 25 | // found in Meta (info) section of spec file 26 | DefaultHost string = "localhost" 27 | // DefaultBasePath is the default BasePath 28 | // found in Meta (info) section of spec file 29 | DefaultBasePath string = "/" 30 | ) 31 | 32 | // DefaultSchemes are the default schemes found in Meta (info) section of spec file 33 | var DefaultSchemes = []string{"https"} 34 | 35 | // NewHTTPClient creates a new cluster registry HTTP client. 36 | func NewHTTPClient(formats strfmt.Registry) *ClusterRegistry { 37 | return NewHTTPClientWithConfig(formats, nil) 38 | } 39 | 40 | // NewHTTPClientWithConfig creates a new cluster registry HTTP client, 41 | // using a customizable transport config. 42 | func NewHTTPClientWithConfig(formats strfmt.Registry, cfg *TransportConfig) *ClusterRegistry { 43 | // ensure nullable parameters have default 44 | if cfg == nil { 45 | cfg = DefaultTransportConfig() 46 | } 47 | 48 | // create transport and client 49 | transport := httptransport.New(cfg.Host, cfg.BasePath, cfg.Schemes) 50 | return New(transport, formats) 51 | } 52 | 53 | // New creates a new cluster registry client 54 | func New(transport runtime.ClientTransport, formats strfmt.Registry) *ClusterRegistry { 55 | // ensure nullable parameters have default 56 | if formats == nil { 57 | formats = strfmt.Default 58 | } 59 | 60 | cli := new(ClusterRegistry) 61 | cli.Transport = transport 62 | cli.Clusters = clusters.New(transport, formats) 63 | cli.ConfigItems = config_items.New(transport, formats) 64 | cli.InfrastructureAccounts = infrastructure_accounts.New(transport, formats) 65 | cli.NodePoolConfigItems = node_pool_config_items.New(transport, formats) 66 | cli.NodePools = node_pools.New(transport, formats) 67 | return cli 68 | } 69 | 70 | // DefaultTransportConfig creates a TransportConfig with the 71 | // default settings taken from the meta section of the spec file. 72 | func DefaultTransportConfig() *TransportConfig { 73 | return &TransportConfig{ 74 | Host: DefaultHost, 75 | BasePath: DefaultBasePath, 76 | Schemes: DefaultSchemes, 77 | } 78 | } 79 | 80 | // TransportConfig contains the transport related info, 81 | // found in the meta section of the spec file. 82 | type TransportConfig struct { 83 | Host string 84 | BasePath string 85 | Schemes []string 86 | } 87 | 88 | // WithHost overrides the default host, 89 | // provided by the meta section of the spec file. 90 | func (cfg *TransportConfig) WithHost(host string) *TransportConfig { 91 | cfg.Host = host 92 | return cfg 93 | } 94 | 95 | // WithBasePath overrides the default basePath, 96 | // provided by the meta section of the spec file. 97 | func (cfg *TransportConfig) WithBasePath(basePath string) *TransportConfig { 98 | cfg.BasePath = basePath 99 | return cfg 100 | } 101 | 102 | // WithSchemes overrides the default schemes, 103 | // provided by the meta section of the spec file. 104 | func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { 105 | cfg.Schemes = schemes 106 | return cfg 107 | } 108 | 109 | // ClusterRegistry is a client for cluster registry 110 | type ClusterRegistry struct { 111 | Clusters clusters.ClientService 112 | 113 | ConfigItems config_items.ClientService 114 | 115 | InfrastructureAccounts infrastructure_accounts.ClientService 116 | 117 | NodePoolConfigItems node_pool_config_items.ClientService 118 | 119 | NodePools node_pools.ClientService 120 | 121 | Transport runtime.ClientTransport 122 | } 123 | 124 | // SetTransport changes the transport on the client and all its subresources 125 | func (c *ClusterRegistry) SetTransport(transport runtime.ClientTransport) { 126 | c.Transport = transport 127 | c.Clusters.SetTransport(transport) 128 | c.ConfigItems.SetTransport(transport) 129 | c.InfrastructureAccounts.SetTransport(transport) 130 | c.NodePoolConfigItems.SetTransport(transport) 131 | c.NodePools.SetTransport(transport) 132 | } 133 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/clusters/create_cluster_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package clusters 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | 18 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/models" 19 | ) 20 | 21 | // NewCreateClusterParams creates a new CreateClusterParams object, 22 | // with the default timeout for this client. 23 | // 24 | // Default values are not hydrated, since defaults are normally applied by the API server side. 25 | // 26 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 27 | func NewCreateClusterParams() *CreateClusterParams { 28 | return &CreateClusterParams{ 29 | timeout: cr.DefaultTimeout, 30 | } 31 | } 32 | 33 | // NewCreateClusterParamsWithTimeout creates a new CreateClusterParams object 34 | // with the ability to set a timeout on a request. 35 | func NewCreateClusterParamsWithTimeout(timeout time.Duration) *CreateClusterParams { 36 | return &CreateClusterParams{ 37 | timeout: timeout, 38 | } 39 | } 40 | 41 | // NewCreateClusterParamsWithContext creates a new CreateClusterParams object 42 | // with the ability to set a context for a request. 43 | func NewCreateClusterParamsWithContext(ctx context.Context) *CreateClusterParams { 44 | return &CreateClusterParams{ 45 | Context: ctx, 46 | } 47 | } 48 | 49 | // NewCreateClusterParamsWithHTTPClient creates a new CreateClusterParams object 50 | // with the ability to set a custom HTTPClient for a request. 51 | func NewCreateClusterParamsWithHTTPClient(client *http.Client) *CreateClusterParams { 52 | return &CreateClusterParams{ 53 | HTTPClient: client, 54 | } 55 | } 56 | 57 | /* 58 | CreateClusterParams contains all the parameters to send to the API endpoint 59 | 60 | for the create cluster operation. 61 | 62 | Typically these are written to a http.Request. 63 | */ 64 | type CreateClusterParams struct { 65 | 66 | /* Cluster. 67 | 68 | Cluster that will be created. 69 | */ 70 | Cluster *models.Cluster 71 | 72 | timeout time.Duration 73 | Context context.Context 74 | HTTPClient *http.Client 75 | } 76 | 77 | // WithDefaults hydrates default values in the create cluster params (not the query body). 78 | // 79 | // All values with no default are reset to their zero value. 80 | func (o *CreateClusterParams) WithDefaults() *CreateClusterParams { 81 | o.SetDefaults() 82 | return o 83 | } 84 | 85 | // SetDefaults hydrates default values in the create cluster params (not the query body). 86 | // 87 | // All values with no default are reset to their zero value. 88 | func (o *CreateClusterParams) SetDefaults() { 89 | // no default values defined for this parameter 90 | } 91 | 92 | // WithTimeout adds the timeout to the create cluster params 93 | func (o *CreateClusterParams) WithTimeout(timeout time.Duration) *CreateClusterParams { 94 | o.SetTimeout(timeout) 95 | return o 96 | } 97 | 98 | // SetTimeout adds the timeout to the create cluster params 99 | func (o *CreateClusterParams) SetTimeout(timeout time.Duration) { 100 | o.timeout = timeout 101 | } 102 | 103 | // WithContext adds the context to the create cluster params 104 | func (o *CreateClusterParams) WithContext(ctx context.Context) *CreateClusterParams { 105 | o.SetContext(ctx) 106 | return o 107 | } 108 | 109 | // SetContext adds the context to the create cluster params 110 | func (o *CreateClusterParams) SetContext(ctx context.Context) { 111 | o.Context = ctx 112 | } 113 | 114 | // WithHTTPClient adds the HTTPClient to the create cluster params 115 | func (o *CreateClusterParams) WithHTTPClient(client *http.Client) *CreateClusterParams { 116 | o.SetHTTPClient(client) 117 | return o 118 | } 119 | 120 | // SetHTTPClient adds the HTTPClient to the create cluster params 121 | func (o *CreateClusterParams) SetHTTPClient(client *http.Client) { 122 | o.HTTPClient = client 123 | } 124 | 125 | // WithCluster adds the cluster to the create cluster params 126 | func (o *CreateClusterParams) WithCluster(cluster *models.Cluster) *CreateClusterParams { 127 | o.SetCluster(cluster) 128 | return o 129 | } 130 | 131 | // SetCluster adds the cluster to the create cluster params 132 | func (o *CreateClusterParams) SetCluster(cluster *models.Cluster) { 133 | o.Cluster = cluster 134 | } 135 | 136 | // WriteToRequest writes these params to a swagger request 137 | func (o *CreateClusterParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 138 | 139 | if err := r.SetTimeout(o.timeout); err != nil { 140 | return err 141 | } 142 | var res []error 143 | if o.Cluster != nil { 144 | if err := r.SetBodyParam(o.Cluster); err != nil { 145 | return err 146 | } 147 | } 148 | 149 | if len(res) > 0 { 150 | return errors.CompositeValidationError(res...) 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/clusters/delete_cluster_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package clusters 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | ) 18 | 19 | // NewDeleteClusterParams creates a new DeleteClusterParams object, 20 | // with the default timeout for this client. 21 | // 22 | // Default values are not hydrated, since defaults are normally applied by the API server side. 23 | // 24 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 25 | func NewDeleteClusterParams() *DeleteClusterParams { 26 | return &DeleteClusterParams{ 27 | timeout: cr.DefaultTimeout, 28 | } 29 | } 30 | 31 | // NewDeleteClusterParamsWithTimeout creates a new DeleteClusterParams object 32 | // with the ability to set a timeout on a request. 33 | func NewDeleteClusterParamsWithTimeout(timeout time.Duration) *DeleteClusterParams { 34 | return &DeleteClusterParams{ 35 | timeout: timeout, 36 | } 37 | } 38 | 39 | // NewDeleteClusterParamsWithContext creates a new DeleteClusterParams object 40 | // with the ability to set a context for a request. 41 | func NewDeleteClusterParamsWithContext(ctx context.Context) *DeleteClusterParams { 42 | return &DeleteClusterParams{ 43 | Context: ctx, 44 | } 45 | } 46 | 47 | // NewDeleteClusterParamsWithHTTPClient creates a new DeleteClusterParams object 48 | // with the ability to set a custom HTTPClient for a request. 49 | func NewDeleteClusterParamsWithHTTPClient(client *http.Client) *DeleteClusterParams { 50 | return &DeleteClusterParams{ 51 | HTTPClient: client, 52 | } 53 | } 54 | 55 | /* 56 | DeleteClusterParams contains all the parameters to send to the API endpoint 57 | 58 | for the delete cluster operation. 59 | 60 | Typically these are written to a http.Request. 61 | */ 62 | type DeleteClusterParams struct { 63 | 64 | /* ClusterID. 65 | 66 | ID of the cluster. 67 | */ 68 | ClusterID string 69 | 70 | timeout time.Duration 71 | Context context.Context 72 | HTTPClient *http.Client 73 | } 74 | 75 | // WithDefaults hydrates default values in the delete cluster params (not the query body). 76 | // 77 | // All values with no default are reset to their zero value. 78 | func (o *DeleteClusterParams) WithDefaults() *DeleteClusterParams { 79 | o.SetDefaults() 80 | return o 81 | } 82 | 83 | // SetDefaults hydrates default values in the delete cluster params (not the query body). 84 | // 85 | // All values with no default are reset to their zero value. 86 | func (o *DeleteClusterParams) SetDefaults() { 87 | // no default values defined for this parameter 88 | } 89 | 90 | // WithTimeout adds the timeout to the delete cluster params 91 | func (o *DeleteClusterParams) WithTimeout(timeout time.Duration) *DeleteClusterParams { 92 | o.SetTimeout(timeout) 93 | return o 94 | } 95 | 96 | // SetTimeout adds the timeout to the delete cluster params 97 | func (o *DeleteClusterParams) SetTimeout(timeout time.Duration) { 98 | o.timeout = timeout 99 | } 100 | 101 | // WithContext adds the context to the delete cluster params 102 | func (o *DeleteClusterParams) WithContext(ctx context.Context) *DeleteClusterParams { 103 | o.SetContext(ctx) 104 | return o 105 | } 106 | 107 | // SetContext adds the context to the delete cluster params 108 | func (o *DeleteClusterParams) SetContext(ctx context.Context) { 109 | o.Context = ctx 110 | } 111 | 112 | // WithHTTPClient adds the HTTPClient to the delete cluster params 113 | func (o *DeleteClusterParams) WithHTTPClient(client *http.Client) *DeleteClusterParams { 114 | o.SetHTTPClient(client) 115 | return o 116 | } 117 | 118 | // SetHTTPClient adds the HTTPClient to the delete cluster params 119 | func (o *DeleteClusterParams) SetHTTPClient(client *http.Client) { 120 | o.HTTPClient = client 121 | } 122 | 123 | // WithClusterID adds the clusterID to the delete cluster params 124 | func (o *DeleteClusterParams) WithClusterID(clusterID string) *DeleteClusterParams { 125 | o.SetClusterID(clusterID) 126 | return o 127 | } 128 | 129 | // SetClusterID adds the clusterId to the delete cluster params 130 | func (o *DeleteClusterParams) SetClusterID(clusterID string) { 131 | o.ClusterID = clusterID 132 | } 133 | 134 | // WriteToRequest writes these params to a swagger request 135 | func (o *DeleteClusterParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 136 | 137 | if err := r.SetTimeout(o.timeout); err != nil { 138 | return err 139 | } 140 | var res []error 141 | 142 | // path param cluster_id 143 | if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { 144 | return err 145 | } 146 | 147 | if len(res) > 0 { 148 | return errors.CompositeValidationError(res...) 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/clusters/get_cluster_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package clusters 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | "github.com/go-openapi/swag" 18 | ) 19 | 20 | // NewGetClusterParams creates a new GetClusterParams object, 21 | // with the default timeout for this client. 22 | // 23 | // Default values are not hydrated, since defaults are normally applied by the API server side. 24 | // 25 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 26 | func NewGetClusterParams() *GetClusterParams { 27 | return &GetClusterParams{ 28 | timeout: cr.DefaultTimeout, 29 | } 30 | } 31 | 32 | // NewGetClusterParamsWithTimeout creates a new GetClusterParams object 33 | // with the ability to set a timeout on a request. 34 | func NewGetClusterParamsWithTimeout(timeout time.Duration) *GetClusterParams { 35 | return &GetClusterParams{ 36 | timeout: timeout, 37 | } 38 | } 39 | 40 | // NewGetClusterParamsWithContext creates a new GetClusterParams object 41 | // with the ability to set a context for a request. 42 | func NewGetClusterParamsWithContext(ctx context.Context) *GetClusterParams { 43 | return &GetClusterParams{ 44 | Context: ctx, 45 | } 46 | } 47 | 48 | // NewGetClusterParamsWithHTTPClient creates a new GetClusterParams object 49 | // with the ability to set a custom HTTPClient for a request. 50 | func NewGetClusterParamsWithHTTPClient(client *http.Client) *GetClusterParams { 51 | return &GetClusterParams{ 52 | HTTPClient: client, 53 | } 54 | } 55 | 56 | /* 57 | GetClusterParams contains all the parameters to send to the API endpoint 58 | 59 | for the get cluster operation. 60 | 61 | Typically these are written to a http.Request. 62 | */ 63 | type GetClusterParams struct { 64 | 65 | /* ClusterID. 66 | 67 | ID of the cluster. 68 | */ 69 | ClusterID string 70 | 71 | /* Verbose. 72 | 73 | Include technical data (config items, node pools) in the response, true by default 74 | 75 | Default: true 76 | */ 77 | Verbose *bool 78 | 79 | timeout time.Duration 80 | Context context.Context 81 | HTTPClient *http.Client 82 | } 83 | 84 | // WithDefaults hydrates default values in the get cluster params (not the query body). 85 | // 86 | // All values with no default are reset to their zero value. 87 | func (o *GetClusterParams) WithDefaults() *GetClusterParams { 88 | o.SetDefaults() 89 | return o 90 | } 91 | 92 | // SetDefaults hydrates default values in the get cluster params (not the query body). 93 | // 94 | // All values with no default are reset to their zero value. 95 | func (o *GetClusterParams) SetDefaults() { 96 | var ( 97 | verboseDefault = bool(true) 98 | ) 99 | 100 | val := GetClusterParams{ 101 | Verbose: &verboseDefault, 102 | } 103 | 104 | val.timeout = o.timeout 105 | val.Context = o.Context 106 | val.HTTPClient = o.HTTPClient 107 | *o = val 108 | } 109 | 110 | // WithTimeout adds the timeout to the get cluster params 111 | func (o *GetClusterParams) WithTimeout(timeout time.Duration) *GetClusterParams { 112 | o.SetTimeout(timeout) 113 | return o 114 | } 115 | 116 | // SetTimeout adds the timeout to the get cluster params 117 | func (o *GetClusterParams) SetTimeout(timeout time.Duration) { 118 | o.timeout = timeout 119 | } 120 | 121 | // WithContext adds the context to the get cluster params 122 | func (o *GetClusterParams) WithContext(ctx context.Context) *GetClusterParams { 123 | o.SetContext(ctx) 124 | return o 125 | } 126 | 127 | // SetContext adds the context to the get cluster params 128 | func (o *GetClusterParams) SetContext(ctx context.Context) { 129 | o.Context = ctx 130 | } 131 | 132 | // WithHTTPClient adds the HTTPClient to the get cluster params 133 | func (o *GetClusterParams) WithHTTPClient(client *http.Client) *GetClusterParams { 134 | o.SetHTTPClient(client) 135 | return o 136 | } 137 | 138 | // SetHTTPClient adds the HTTPClient to the get cluster params 139 | func (o *GetClusterParams) SetHTTPClient(client *http.Client) { 140 | o.HTTPClient = client 141 | } 142 | 143 | // WithClusterID adds the clusterID to the get cluster params 144 | func (o *GetClusterParams) WithClusterID(clusterID string) *GetClusterParams { 145 | o.SetClusterID(clusterID) 146 | return o 147 | } 148 | 149 | // SetClusterID adds the clusterId to the get cluster params 150 | func (o *GetClusterParams) SetClusterID(clusterID string) { 151 | o.ClusterID = clusterID 152 | } 153 | 154 | // WithVerbose adds the verbose to the get cluster params 155 | func (o *GetClusterParams) WithVerbose(verbose *bool) *GetClusterParams { 156 | o.SetVerbose(verbose) 157 | return o 158 | } 159 | 160 | // SetVerbose adds the verbose to the get cluster params 161 | func (o *GetClusterParams) SetVerbose(verbose *bool) { 162 | o.Verbose = verbose 163 | } 164 | 165 | // WriteToRequest writes these params to a swagger request 166 | func (o *GetClusterParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 167 | 168 | if err := r.SetTimeout(o.timeout); err != nil { 169 | return err 170 | } 171 | var res []error 172 | 173 | // path param cluster_id 174 | if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { 175 | return err 176 | } 177 | 178 | if o.Verbose != nil { 179 | 180 | // query param verbose 181 | var qrVerbose bool 182 | 183 | if o.Verbose != nil { 184 | qrVerbose = *o.Verbose 185 | } 186 | qVerbose := swag.FormatBool(qrVerbose) 187 | if qVerbose != "" { 188 | 189 | if err := r.SetQueryParam("verbose", qVerbose); err != nil { 190 | return err 191 | } 192 | } 193 | } 194 | 195 | if len(res) > 0 { 196 | return errors.CompositeValidationError(res...) 197 | } 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/clusters/update_cluster_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package clusters 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | 18 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/models" 19 | ) 20 | 21 | // NewUpdateClusterParams creates a new UpdateClusterParams object, 22 | // with the default timeout for this client. 23 | // 24 | // Default values are not hydrated, since defaults are normally applied by the API server side. 25 | // 26 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 27 | func NewUpdateClusterParams() *UpdateClusterParams { 28 | return &UpdateClusterParams{ 29 | timeout: cr.DefaultTimeout, 30 | } 31 | } 32 | 33 | // NewUpdateClusterParamsWithTimeout creates a new UpdateClusterParams object 34 | // with the ability to set a timeout on a request. 35 | func NewUpdateClusterParamsWithTimeout(timeout time.Duration) *UpdateClusterParams { 36 | return &UpdateClusterParams{ 37 | timeout: timeout, 38 | } 39 | } 40 | 41 | // NewUpdateClusterParamsWithContext creates a new UpdateClusterParams object 42 | // with the ability to set a context for a request. 43 | func NewUpdateClusterParamsWithContext(ctx context.Context) *UpdateClusterParams { 44 | return &UpdateClusterParams{ 45 | Context: ctx, 46 | } 47 | } 48 | 49 | // NewUpdateClusterParamsWithHTTPClient creates a new UpdateClusterParams object 50 | // with the ability to set a custom HTTPClient for a request. 51 | func NewUpdateClusterParamsWithHTTPClient(client *http.Client) *UpdateClusterParams { 52 | return &UpdateClusterParams{ 53 | HTTPClient: client, 54 | } 55 | } 56 | 57 | /* 58 | UpdateClusterParams contains all the parameters to send to the API endpoint 59 | 60 | for the update cluster operation. 61 | 62 | Typically these are written to a http.Request. 63 | */ 64 | type UpdateClusterParams struct { 65 | 66 | /* Cluster. 67 | 68 | Cluster that will be updated. 69 | */ 70 | Cluster *models.ClusterUpdate 71 | 72 | /* ClusterID. 73 | 74 | ID of the cluster. 75 | */ 76 | ClusterID string 77 | 78 | timeout time.Duration 79 | Context context.Context 80 | HTTPClient *http.Client 81 | } 82 | 83 | // WithDefaults hydrates default values in the update cluster params (not the query body). 84 | // 85 | // All values with no default are reset to their zero value. 86 | func (o *UpdateClusterParams) WithDefaults() *UpdateClusterParams { 87 | o.SetDefaults() 88 | return o 89 | } 90 | 91 | // SetDefaults hydrates default values in the update cluster params (not the query body). 92 | // 93 | // All values with no default are reset to their zero value. 94 | func (o *UpdateClusterParams) SetDefaults() { 95 | // no default values defined for this parameter 96 | } 97 | 98 | // WithTimeout adds the timeout to the update cluster params 99 | func (o *UpdateClusterParams) WithTimeout(timeout time.Duration) *UpdateClusterParams { 100 | o.SetTimeout(timeout) 101 | return o 102 | } 103 | 104 | // SetTimeout adds the timeout to the update cluster params 105 | func (o *UpdateClusterParams) SetTimeout(timeout time.Duration) { 106 | o.timeout = timeout 107 | } 108 | 109 | // WithContext adds the context to the update cluster params 110 | func (o *UpdateClusterParams) WithContext(ctx context.Context) *UpdateClusterParams { 111 | o.SetContext(ctx) 112 | return o 113 | } 114 | 115 | // SetContext adds the context to the update cluster params 116 | func (o *UpdateClusterParams) SetContext(ctx context.Context) { 117 | o.Context = ctx 118 | } 119 | 120 | // WithHTTPClient adds the HTTPClient to the update cluster params 121 | func (o *UpdateClusterParams) WithHTTPClient(client *http.Client) *UpdateClusterParams { 122 | o.SetHTTPClient(client) 123 | return o 124 | } 125 | 126 | // SetHTTPClient adds the HTTPClient to the update cluster params 127 | func (o *UpdateClusterParams) SetHTTPClient(client *http.Client) { 128 | o.HTTPClient = client 129 | } 130 | 131 | // WithCluster adds the cluster to the update cluster params 132 | func (o *UpdateClusterParams) WithCluster(cluster *models.ClusterUpdate) *UpdateClusterParams { 133 | o.SetCluster(cluster) 134 | return o 135 | } 136 | 137 | // SetCluster adds the cluster to the update cluster params 138 | func (o *UpdateClusterParams) SetCluster(cluster *models.ClusterUpdate) { 139 | o.Cluster = cluster 140 | } 141 | 142 | // WithClusterID adds the clusterID to the update cluster params 143 | func (o *UpdateClusterParams) WithClusterID(clusterID string) *UpdateClusterParams { 144 | o.SetClusterID(clusterID) 145 | return o 146 | } 147 | 148 | // SetClusterID adds the clusterId to the update cluster params 149 | func (o *UpdateClusterParams) SetClusterID(clusterID string) { 150 | o.ClusterID = clusterID 151 | } 152 | 153 | // WriteToRequest writes these params to a swagger request 154 | func (o *UpdateClusterParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 155 | 156 | if err := r.SetTimeout(o.timeout); err != nil { 157 | return err 158 | } 159 | var res []error 160 | if o.Cluster != nil { 161 | if err := r.SetBodyParam(o.Cluster); err != nil { 162 | return err 163 | } 164 | } 165 | 166 | // path param cluster_id 167 | if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { 168 | return err 169 | } 170 | 171 | if len(res) > 0 { 172 | return errors.CompositeValidationError(res...) 173 | } 174 | return nil 175 | } 176 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/config_items/config_items_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package config_items 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/go-openapi/runtime" 12 | httptransport "github.com/go-openapi/runtime/client" 13 | "github.com/go-openapi/strfmt" 14 | ) 15 | 16 | // New creates a new config items API client. 17 | func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { 18 | return &Client{transport: transport, formats: formats} 19 | } 20 | 21 | // New creates a new config items API client with basic auth credentials. 22 | // It takes the following parameters: 23 | // - host: http host (github.com). 24 | // - basePath: any base path for the API client ("/v1", "/v3"). 25 | // - scheme: http scheme ("http", "https"). 26 | // - user: user for basic authentication header. 27 | // - password: password for basic authentication header. 28 | func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService { 29 | transport := httptransport.New(host, basePath, []string{scheme}) 30 | transport.DefaultAuthentication = httptransport.BasicAuth(user, password) 31 | return &Client{transport: transport, formats: strfmt.Default} 32 | } 33 | 34 | // New creates a new config items API client with a bearer token for authentication. 35 | // It takes the following parameters: 36 | // - host: http host (github.com). 37 | // - basePath: any base path for the API client ("/v1", "/v3"). 38 | // - scheme: http scheme ("http", "https"). 39 | // - bearerToken: bearer token for Bearer authentication header. 40 | func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService { 41 | transport := httptransport.New(host, basePath, []string{scheme}) 42 | transport.DefaultAuthentication = httptransport.BearerToken(bearerToken) 43 | return &Client{transport: transport, formats: strfmt.Default} 44 | } 45 | 46 | /* 47 | Client for config items API 48 | */ 49 | type Client struct { 50 | transport runtime.ClientTransport 51 | formats strfmt.Registry 52 | } 53 | 54 | // ClientOption may be used to customize the behavior of Client methods. 55 | type ClientOption func(*runtime.ClientOperation) 56 | 57 | // ClientService is the interface for Client methods 58 | type ClientService interface { 59 | AddOrUpdateConfigItem(params *AddOrUpdateConfigItemParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*AddOrUpdateConfigItemOK, error) 60 | 61 | DeleteConfigItem(params *DeleteConfigItemParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*DeleteConfigItemNoContent, error) 62 | 63 | SetTransport(transport runtime.ClientTransport) 64 | } 65 | 66 | /* 67 | AddOrUpdateConfigItem adds update config item 68 | 69 | Add/update a configuration item unique to the cluster. 70 | */ 71 | func (a *Client) AddOrUpdateConfigItem(params *AddOrUpdateConfigItemParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*AddOrUpdateConfigItemOK, error) { 72 | // TODO: Validate the params before sending 73 | if params == nil { 74 | params = NewAddOrUpdateConfigItemParams() 75 | } 76 | op := &runtime.ClientOperation{ 77 | ID: "addOrUpdateConfigItem", 78 | Method: "PUT", 79 | PathPattern: "/kubernetes-clusters/{cluster_id}/config-items/{config_key}", 80 | ProducesMediaTypes: []string{"application/json"}, 81 | ConsumesMediaTypes: []string{"application/json"}, 82 | Schemes: []string{"https"}, 83 | Params: params, 84 | Reader: &AddOrUpdateConfigItemReader{formats: a.formats}, 85 | AuthInfo: authInfo, 86 | Context: params.Context, 87 | Client: params.HTTPClient, 88 | } 89 | for _, opt := range opts { 90 | opt(op) 91 | } 92 | 93 | result, err := a.transport.Submit(op) 94 | if err != nil { 95 | return nil, err 96 | } 97 | success, ok := result.(*AddOrUpdateConfigItemOK) 98 | if ok { 99 | return success, nil 100 | } 101 | // unexpected success response 102 | // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue 103 | msg := fmt.Sprintf("unexpected success response for addOrUpdateConfigItem: API contract not enforced by server. Client expected to get an error, but got: %T", result) 104 | panic(msg) 105 | } 106 | 107 | /* 108 | DeleteConfigItem deletes config item 109 | 110 | Deletes config item. 111 | */ 112 | func (a *Client) DeleteConfigItem(params *DeleteConfigItemParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*DeleteConfigItemNoContent, error) { 113 | // TODO: Validate the params before sending 114 | if params == nil { 115 | params = NewDeleteConfigItemParams() 116 | } 117 | op := &runtime.ClientOperation{ 118 | ID: "deleteConfigItem", 119 | Method: "DELETE", 120 | PathPattern: "/kubernetes-clusters/{cluster_id}/config-items/{config_key}", 121 | ProducesMediaTypes: []string{"application/json"}, 122 | ConsumesMediaTypes: []string{"application/json"}, 123 | Schemes: []string{"https"}, 124 | Params: params, 125 | Reader: &DeleteConfigItemReader{formats: a.formats}, 126 | AuthInfo: authInfo, 127 | Context: params.Context, 128 | Client: params.HTTPClient, 129 | } 130 | for _, opt := range opts { 131 | opt(op) 132 | } 133 | 134 | result, err := a.transport.Submit(op) 135 | if err != nil { 136 | return nil, err 137 | } 138 | success, ok := result.(*DeleteConfigItemNoContent) 139 | if ok { 140 | return success, nil 141 | } 142 | // unexpected success response 143 | // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue 144 | msg := fmt.Sprintf("unexpected success response for deleteConfigItem: API contract not enforced by server. Client expected to get an error, but got: %T", result) 145 | panic(msg) 146 | } 147 | 148 | // SetTransport changes the transport on the client 149 | func (a *Client) SetTransport(transport runtime.ClientTransport) { 150 | a.transport = transport 151 | } 152 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/config_items/delete_config_item_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package config_items 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | ) 18 | 19 | // NewDeleteConfigItemParams creates a new DeleteConfigItemParams object, 20 | // with the default timeout for this client. 21 | // 22 | // Default values are not hydrated, since defaults are normally applied by the API server side. 23 | // 24 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 25 | func NewDeleteConfigItemParams() *DeleteConfigItemParams { 26 | return &DeleteConfigItemParams{ 27 | timeout: cr.DefaultTimeout, 28 | } 29 | } 30 | 31 | // NewDeleteConfigItemParamsWithTimeout creates a new DeleteConfigItemParams object 32 | // with the ability to set a timeout on a request. 33 | func NewDeleteConfigItemParamsWithTimeout(timeout time.Duration) *DeleteConfigItemParams { 34 | return &DeleteConfigItemParams{ 35 | timeout: timeout, 36 | } 37 | } 38 | 39 | // NewDeleteConfigItemParamsWithContext creates a new DeleteConfigItemParams object 40 | // with the ability to set a context for a request. 41 | func NewDeleteConfigItemParamsWithContext(ctx context.Context) *DeleteConfigItemParams { 42 | return &DeleteConfigItemParams{ 43 | Context: ctx, 44 | } 45 | } 46 | 47 | // NewDeleteConfigItemParamsWithHTTPClient creates a new DeleteConfigItemParams object 48 | // with the ability to set a custom HTTPClient for a request. 49 | func NewDeleteConfigItemParamsWithHTTPClient(client *http.Client) *DeleteConfigItemParams { 50 | return &DeleteConfigItemParams{ 51 | HTTPClient: client, 52 | } 53 | } 54 | 55 | /* 56 | DeleteConfigItemParams contains all the parameters to send to the API endpoint 57 | 58 | for the delete config item operation. 59 | 60 | Typically these are written to a http.Request. 61 | */ 62 | type DeleteConfigItemParams struct { 63 | 64 | /* ClusterID. 65 | 66 | ID of the cluster. 67 | */ 68 | ClusterID string 69 | 70 | /* ConfigKey. 71 | 72 | Key for the config value. 73 | */ 74 | ConfigKey string 75 | 76 | timeout time.Duration 77 | Context context.Context 78 | HTTPClient *http.Client 79 | } 80 | 81 | // WithDefaults hydrates default values in the delete config item params (not the query body). 82 | // 83 | // All values with no default are reset to their zero value. 84 | func (o *DeleteConfigItemParams) WithDefaults() *DeleteConfigItemParams { 85 | o.SetDefaults() 86 | return o 87 | } 88 | 89 | // SetDefaults hydrates default values in the delete config item params (not the query body). 90 | // 91 | // All values with no default are reset to their zero value. 92 | func (o *DeleteConfigItemParams) SetDefaults() { 93 | // no default values defined for this parameter 94 | } 95 | 96 | // WithTimeout adds the timeout to the delete config item params 97 | func (o *DeleteConfigItemParams) WithTimeout(timeout time.Duration) *DeleteConfigItemParams { 98 | o.SetTimeout(timeout) 99 | return o 100 | } 101 | 102 | // SetTimeout adds the timeout to the delete config item params 103 | func (o *DeleteConfigItemParams) SetTimeout(timeout time.Duration) { 104 | o.timeout = timeout 105 | } 106 | 107 | // WithContext adds the context to the delete config item params 108 | func (o *DeleteConfigItemParams) WithContext(ctx context.Context) *DeleteConfigItemParams { 109 | o.SetContext(ctx) 110 | return o 111 | } 112 | 113 | // SetContext adds the context to the delete config item params 114 | func (o *DeleteConfigItemParams) SetContext(ctx context.Context) { 115 | o.Context = ctx 116 | } 117 | 118 | // WithHTTPClient adds the HTTPClient to the delete config item params 119 | func (o *DeleteConfigItemParams) WithHTTPClient(client *http.Client) *DeleteConfigItemParams { 120 | o.SetHTTPClient(client) 121 | return o 122 | } 123 | 124 | // SetHTTPClient adds the HTTPClient to the delete config item params 125 | func (o *DeleteConfigItemParams) SetHTTPClient(client *http.Client) { 126 | o.HTTPClient = client 127 | } 128 | 129 | // WithClusterID adds the clusterID to the delete config item params 130 | func (o *DeleteConfigItemParams) WithClusterID(clusterID string) *DeleteConfigItemParams { 131 | o.SetClusterID(clusterID) 132 | return o 133 | } 134 | 135 | // SetClusterID adds the clusterId to the delete config item params 136 | func (o *DeleteConfigItemParams) SetClusterID(clusterID string) { 137 | o.ClusterID = clusterID 138 | } 139 | 140 | // WithConfigKey adds the configKey to the delete config item params 141 | func (o *DeleteConfigItemParams) WithConfigKey(configKey string) *DeleteConfigItemParams { 142 | o.SetConfigKey(configKey) 143 | return o 144 | } 145 | 146 | // SetConfigKey adds the configKey to the delete config item params 147 | func (o *DeleteConfigItemParams) SetConfigKey(configKey string) { 148 | o.ConfigKey = configKey 149 | } 150 | 151 | // WriteToRequest writes these params to a swagger request 152 | func (o *DeleteConfigItemParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 153 | 154 | if err := r.SetTimeout(o.timeout); err != nil { 155 | return err 156 | } 157 | var res []error 158 | 159 | // path param cluster_id 160 | if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { 161 | return err 162 | } 163 | 164 | // path param config_key 165 | if err := r.SetPathParam("config_key", o.ConfigKey); err != nil { 166 | return err 167 | } 168 | 169 | if len(res) > 0 { 170 | return errors.CompositeValidationError(res...) 171 | } 172 | return nil 173 | } 174 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/infrastructure_accounts/create_infrastructure_account_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package infrastructure_accounts 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | 18 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/models" 19 | ) 20 | 21 | // NewCreateInfrastructureAccountParams creates a new CreateInfrastructureAccountParams object, 22 | // with the default timeout for this client. 23 | // 24 | // Default values are not hydrated, since defaults are normally applied by the API server side. 25 | // 26 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 27 | func NewCreateInfrastructureAccountParams() *CreateInfrastructureAccountParams { 28 | return &CreateInfrastructureAccountParams{ 29 | timeout: cr.DefaultTimeout, 30 | } 31 | } 32 | 33 | // NewCreateInfrastructureAccountParamsWithTimeout creates a new CreateInfrastructureAccountParams object 34 | // with the ability to set a timeout on a request. 35 | func NewCreateInfrastructureAccountParamsWithTimeout(timeout time.Duration) *CreateInfrastructureAccountParams { 36 | return &CreateInfrastructureAccountParams{ 37 | timeout: timeout, 38 | } 39 | } 40 | 41 | // NewCreateInfrastructureAccountParamsWithContext creates a new CreateInfrastructureAccountParams object 42 | // with the ability to set a context for a request. 43 | func NewCreateInfrastructureAccountParamsWithContext(ctx context.Context) *CreateInfrastructureAccountParams { 44 | return &CreateInfrastructureAccountParams{ 45 | Context: ctx, 46 | } 47 | } 48 | 49 | // NewCreateInfrastructureAccountParamsWithHTTPClient creates a new CreateInfrastructureAccountParams object 50 | // with the ability to set a custom HTTPClient for a request. 51 | func NewCreateInfrastructureAccountParamsWithHTTPClient(client *http.Client) *CreateInfrastructureAccountParams { 52 | return &CreateInfrastructureAccountParams{ 53 | HTTPClient: client, 54 | } 55 | } 56 | 57 | /* 58 | CreateInfrastructureAccountParams contains all the parameters to send to the API endpoint 59 | 60 | for the create infrastructure account operation. 61 | 62 | Typically these are written to a http.Request. 63 | */ 64 | type CreateInfrastructureAccountParams struct { 65 | 66 | /* InfrastructureAccount. 67 | 68 | Account that will be created. 69 | */ 70 | InfrastructureAccount *models.InfrastructureAccount 71 | 72 | timeout time.Duration 73 | Context context.Context 74 | HTTPClient *http.Client 75 | } 76 | 77 | // WithDefaults hydrates default values in the create infrastructure account params (not the query body). 78 | // 79 | // All values with no default are reset to their zero value. 80 | func (o *CreateInfrastructureAccountParams) WithDefaults() *CreateInfrastructureAccountParams { 81 | o.SetDefaults() 82 | return o 83 | } 84 | 85 | // SetDefaults hydrates default values in the create infrastructure account params (not the query body). 86 | // 87 | // All values with no default are reset to their zero value. 88 | func (o *CreateInfrastructureAccountParams) SetDefaults() { 89 | // no default values defined for this parameter 90 | } 91 | 92 | // WithTimeout adds the timeout to the create infrastructure account params 93 | func (o *CreateInfrastructureAccountParams) WithTimeout(timeout time.Duration) *CreateInfrastructureAccountParams { 94 | o.SetTimeout(timeout) 95 | return o 96 | } 97 | 98 | // SetTimeout adds the timeout to the create infrastructure account params 99 | func (o *CreateInfrastructureAccountParams) SetTimeout(timeout time.Duration) { 100 | o.timeout = timeout 101 | } 102 | 103 | // WithContext adds the context to the create infrastructure account params 104 | func (o *CreateInfrastructureAccountParams) WithContext(ctx context.Context) *CreateInfrastructureAccountParams { 105 | o.SetContext(ctx) 106 | return o 107 | } 108 | 109 | // SetContext adds the context to the create infrastructure account params 110 | func (o *CreateInfrastructureAccountParams) SetContext(ctx context.Context) { 111 | o.Context = ctx 112 | } 113 | 114 | // WithHTTPClient adds the HTTPClient to the create infrastructure account params 115 | func (o *CreateInfrastructureAccountParams) WithHTTPClient(client *http.Client) *CreateInfrastructureAccountParams { 116 | o.SetHTTPClient(client) 117 | return o 118 | } 119 | 120 | // SetHTTPClient adds the HTTPClient to the create infrastructure account params 121 | func (o *CreateInfrastructureAccountParams) SetHTTPClient(client *http.Client) { 122 | o.HTTPClient = client 123 | } 124 | 125 | // WithInfrastructureAccount adds the infrastructureAccount to the create infrastructure account params 126 | func (o *CreateInfrastructureAccountParams) WithInfrastructureAccount(infrastructureAccount *models.InfrastructureAccount) *CreateInfrastructureAccountParams { 127 | o.SetInfrastructureAccount(infrastructureAccount) 128 | return o 129 | } 130 | 131 | // SetInfrastructureAccount adds the infrastructureAccount to the create infrastructure account params 132 | func (o *CreateInfrastructureAccountParams) SetInfrastructureAccount(infrastructureAccount *models.InfrastructureAccount) { 133 | o.InfrastructureAccount = infrastructureAccount 134 | } 135 | 136 | // WriteToRequest writes these params to a swagger request 137 | func (o *CreateInfrastructureAccountParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 138 | 139 | if err := r.SetTimeout(o.timeout); err != nil { 140 | return err 141 | } 142 | var res []error 143 | if o.InfrastructureAccount != nil { 144 | if err := r.SetBodyParam(o.InfrastructureAccount); err != nil { 145 | return err 146 | } 147 | } 148 | 149 | if len(res) > 0 { 150 | return errors.CompositeValidationError(res...) 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/infrastructure_accounts/get_infrastructure_account_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package infrastructure_accounts 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | ) 18 | 19 | // NewGetInfrastructureAccountParams creates a new GetInfrastructureAccountParams object, 20 | // with the default timeout for this client. 21 | // 22 | // Default values are not hydrated, since defaults are normally applied by the API server side. 23 | // 24 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 25 | func NewGetInfrastructureAccountParams() *GetInfrastructureAccountParams { 26 | return &GetInfrastructureAccountParams{ 27 | timeout: cr.DefaultTimeout, 28 | } 29 | } 30 | 31 | // NewGetInfrastructureAccountParamsWithTimeout creates a new GetInfrastructureAccountParams object 32 | // with the ability to set a timeout on a request. 33 | func NewGetInfrastructureAccountParamsWithTimeout(timeout time.Duration) *GetInfrastructureAccountParams { 34 | return &GetInfrastructureAccountParams{ 35 | timeout: timeout, 36 | } 37 | } 38 | 39 | // NewGetInfrastructureAccountParamsWithContext creates a new GetInfrastructureAccountParams object 40 | // with the ability to set a context for a request. 41 | func NewGetInfrastructureAccountParamsWithContext(ctx context.Context) *GetInfrastructureAccountParams { 42 | return &GetInfrastructureAccountParams{ 43 | Context: ctx, 44 | } 45 | } 46 | 47 | // NewGetInfrastructureAccountParamsWithHTTPClient creates a new GetInfrastructureAccountParams object 48 | // with the ability to set a custom HTTPClient for a request. 49 | func NewGetInfrastructureAccountParamsWithHTTPClient(client *http.Client) *GetInfrastructureAccountParams { 50 | return &GetInfrastructureAccountParams{ 51 | HTTPClient: client, 52 | } 53 | } 54 | 55 | /* 56 | GetInfrastructureAccountParams contains all the parameters to send to the API endpoint 57 | 58 | for the get infrastructure account operation. 59 | 60 | Typically these are written to a http.Request. 61 | */ 62 | type GetInfrastructureAccountParams struct { 63 | 64 | /* AccountID. 65 | 66 | ID of the infrastructure account. 67 | */ 68 | AccountID string 69 | 70 | timeout time.Duration 71 | Context context.Context 72 | HTTPClient *http.Client 73 | } 74 | 75 | // WithDefaults hydrates default values in the get infrastructure account params (not the query body). 76 | // 77 | // All values with no default are reset to their zero value. 78 | func (o *GetInfrastructureAccountParams) WithDefaults() *GetInfrastructureAccountParams { 79 | o.SetDefaults() 80 | return o 81 | } 82 | 83 | // SetDefaults hydrates default values in the get infrastructure account params (not the query body). 84 | // 85 | // All values with no default are reset to their zero value. 86 | func (o *GetInfrastructureAccountParams) SetDefaults() { 87 | // no default values defined for this parameter 88 | } 89 | 90 | // WithTimeout adds the timeout to the get infrastructure account params 91 | func (o *GetInfrastructureAccountParams) WithTimeout(timeout time.Duration) *GetInfrastructureAccountParams { 92 | o.SetTimeout(timeout) 93 | return o 94 | } 95 | 96 | // SetTimeout adds the timeout to the get infrastructure account params 97 | func (o *GetInfrastructureAccountParams) SetTimeout(timeout time.Duration) { 98 | o.timeout = timeout 99 | } 100 | 101 | // WithContext adds the context to the get infrastructure account params 102 | func (o *GetInfrastructureAccountParams) WithContext(ctx context.Context) *GetInfrastructureAccountParams { 103 | o.SetContext(ctx) 104 | return o 105 | } 106 | 107 | // SetContext adds the context to the get infrastructure account params 108 | func (o *GetInfrastructureAccountParams) SetContext(ctx context.Context) { 109 | o.Context = ctx 110 | } 111 | 112 | // WithHTTPClient adds the HTTPClient to the get infrastructure account params 113 | func (o *GetInfrastructureAccountParams) WithHTTPClient(client *http.Client) *GetInfrastructureAccountParams { 114 | o.SetHTTPClient(client) 115 | return o 116 | } 117 | 118 | // SetHTTPClient adds the HTTPClient to the get infrastructure account params 119 | func (o *GetInfrastructureAccountParams) SetHTTPClient(client *http.Client) { 120 | o.HTTPClient = client 121 | } 122 | 123 | // WithAccountID adds the accountID to the get infrastructure account params 124 | func (o *GetInfrastructureAccountParams) WithAccountID(accountID string) *GetInfrastructureAccountParams { 125 | o.SetAccountID(accountID) 126 | return o 127 | } 128 | 129 | // SetAccountID adds the accountId to the get infrastructure account params 130 | func (o *GetInfrastructureAccountParams) SetAccountID(accountID string) { 131 | o.AccountID = accountID 132 | } 133 | 134 | // WriteToRequest writes these params to a swagger request 135 | func (o *GetInfrastructureAccountParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 136 | 137 | if err := r.SetTimeout(o.timeout); err != nil { 138 | return err 139 | } 140 | var res []error 141 | 142 | // path param account_id 143 | if err := r.SetPathParam("account_id", o.AccountID); err != nil { 144 | return err 145 | } 146 | 147 | if len(res) > 0 { 148 | return errors.CompositeValidationError(res...) 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/node_pools/delete_node_pool_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package node_pools 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | ) 18 | 19 | // NewDeleteNodePoolParams creates a new DeleteNodePoolParams object, 20 | // with the default timeout for this client. 21 | // 22 | // Default values are not hydrated, since defaults are normally applied by the API server side. 23 | // 24 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 25 | func NewDeleteNodePoolParams() *DeleteNodePoolParams { 26 | return &DeleteNodePoolParams{ 27 | timeout: cr.DefaultTimeout, 28 | } 29 | } 30 | 31 | // NewDeleteNodePoolParamsWithTimeout creates a new DeleteNodePoolParams object 32 | // with the ability to set a timeout on a request. 33 | func NewDeleteNodePoolParamsWithTimeout(timeout time.Duration) *DeleteNodePoolParams { 34 | return &DeleteNodePoolParams{ 35 | timeout: timeout, 36 | } 37 | } 38 | 39 | // NewDeleteNodePoolParamsWithContext creates a new DeleteNodePoolParams object 40 | // with the ability to set a context for a request. 41 | func NewDeleteNodePoolParamsWithContext(ctx context.Context) *DeleteNodePoolParams { 42 | return &DeleteNodePoolParams{ 43 | Context: ctx, 44 | } 45 | } 46 | 47 | // NewDeleteNodePoolParamsWithHTTPClient creates a new DeleteNodePoolParams object 48 | // with the ability to set a custom HTTPClient for a request. 49 | func NewDeleteNodePoolParamsWithHTTPClient(client *http.Client) *DeleteNodePoolParams { 50 | return &DeleteNodePoolParams{ 51 | HTTPClient: client, 52 | } 53 | } 54 | 55 | /* 56 | DeleteNodePoolParams contains all the parameters to send to the API endpoint 57 | 58 | for the delete node pool operation. 59 | 60 | Typically these are written to a http.Request. 61 | */ 62 | type DeleteNodePoolParams struct { 63 | 64 | /* ClusterID. 65 | 66 | ID of the cluster. 67 | */ 68 | ClusterID string 69 | 70 | /* NodePoolName. 71 | 72 | Name of the node pool. 73 | */ 74 | NodePoolName string 75 | 76 | timeout time.Duration 77 | Context context.Context 78 | HTTPClient *http.Client 79 | } 80 | 81 | // WithDefaults hydrates default values in the delete node pool params (not the query body). 82 | // 83 | // All values with no default are reset to their zero value. 84 | func (o *DeleteNodePoolParams) WithDefaults() *DeleteNodePoolParams { 85 | o.SetDefaults() 86 | return o 87 | } 88 | 89 | // SetDefaults hydrates default values in the delete node pool params (not the query body). 90 | // 91 | // All values with no default are reset to their zero value. 92 | func (o *DeleteNodePoolParams) SetDefaults() { 93 | // no default values defined for this parameter 94 | } 95 | 96 | // WithTimeout adds the timeout to the delete node pool params 97 | func (o *DeleteNodePoolParams) WithTimeout(timeout time.Duration) *DeleteNodePoolParams { 98 | o.SetTimeout(timeout) 99 | return o 100 | } 101 | 102 | // SetTimeout adds the timeout to the delete node pool params 103 | func (o *DeleteNodePoolParams) SetTimeout(timeout time.Duration) { 104 | o.timeout = timeout 105 | } 106 | 107 | // WithContext adds the context to the delete node pool params 108 | func (o *DeleteNodePoolParams) WithContext(ctx context.Context) *DeleteNodePoolParams { 109 | o.SetContext(ctx) 110 | return o 111 | } 112 | 113 | // SetContext adds the context to the delete node pool params 114 | func (o *DeleteNodePoolParams) SetContext(ctx context.Context) { 115 | o.Context = ctx 116 | } 117 | 118 | // WithHTTPClient adds the HTTPClient to the delete node pool params 119 | func (o *DeleteNodePoolParams) WithHTTPClient(client *http.Client) *DeleteNodePoolParams { 120 | o.SetHTTPClient(client) 121 | return o 122 | } 123 | 124 | // SetHTTPClient adds the HTTPClient to the delete node pool params 125 | func (o *DeleteNodePoolParams) SetHTTPClient(client *http.Client) { 126 | o.HTTPClient = client 127 | } 128 | 129 | // WithClusterID adds the clusterID to the delete node pool params 130 | func (o *DeleteNodePoolParams) WithClusterID(clusterID string) *DeleteNodePoolParams { 131 | o.SetClusterID(clusterID) 132 | return o 133 | } 134 | 135 | // SetClusterID adds the clusterId to the delete node pool params 136 | func (o *DeleteNodePoolParams) SetClusterID(clusterID string) { 137 | o.ClusterID = clusterID 138 | } 139 | 140 | // WithNodePoolName adds the nodePoolName to the delete node pool params 141 | func (o *DeleteNodePoolParams) WithNodePoolName(nodePoolName string) *DeleteNodePoolParams { 142 | o.SetNodePoolName(nodePoolName) 143 | return o 144 | } 145 | 146 | // SetNodePoolName adds the nodePoolName to the delete node pool params 147 | func (o *DeleteNodePoolParams) SetNodePoolName(nodePoolName string) { 148 | o.NodePoolName = nodePoolName 149 | } 150 | 151 | // WriteToRequest writes these params to a swagger request 152 | func (o *DeleteNodePoolParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 153 | 154 | if err := r.SetTimeout(o.timeout); err != nil { 155 | return err 156 | } 157 | var res []error 158 | 159 | // path param cluster_id 160 | if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { 161 | return err 162 | } 163 | 164 | // path param node_pool_name 165 | if err := r.SetPathParam("node_pool_name", o.NodePoolName); err != nil { 166 | return err 167 | } 168 | 169 | if len(res) > 0 { 170 | return errors.CompositeValidationError(res...) 171 | } 172 | return nil 173 | } 174 | -------------------------------------------------------------------------------- /pkg/cluster-registry/client/node_pools/list_node_pools_parameters.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package node_pools 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/go-openapi/errors" 14 | "github.com/go-openapi/runtime" 15 | cr "github.com/go-openapi/runtime/client" 16 | "github.com/go-openapi/strfmt" 17 | ) 18 | 19 | // NewListNodePoolsParams creates a new ListNodePoolsParams object, 20 | // with the default timeout for this client. 21 | // 22 | // Default values are not hydrated, since defaults are normally applied by the API server side. 23 | // 24 | // To enforce default values in parameter, use SetDefaults or WithDefaults. 25 | func NewListNodePoolsParams() *ListNodePoolsParams { 26 | return &ListNodePoolsParams{ 27 | timeout: cr.DefaultTimeout, 28 | } 29 | } 30 | 31 | // NewListNodePoolsParamsWithTimeout creates a new ListNodePoolsParams object 32 | // with the ability to set a timeout on a request. 33 | func NewListNodePoolsParamsWithTimeout(timeout time.Duration) *ListNodePoolsParams { 34 | return &ListNodePoolsParams{ 35 | timeout: timeout, 36 | } 37 | } 38 | 39 | // NewListNodePoolsParamsWithContext creates a new ListNodePoolsParams object 40 | // with the ability to set a context for a request. 41 | func NewListNodePoolsParamsWithContext(ctx context.Context) *ListNodePoolsParams { 42 | return &ListNodePoolsParams{ 43 | Context: ctx, 44 | } 45 | } 46 | 47 | // NewListNodePoolsParamsWithHTTPClient creates a new ListNodePoolsParams object 48 | // with the ability to set a custom HTTPClient for a request. 49 | func NewListNodePoolsParamsWithHTTPClient(client *http.Client) *ListNodePoolsParams { 50 | return &ListNodePoolsParams{ 51 | HTTPClient: client, 52 | } 53 | } 54 | 55 | /* 56 | ListNodePoolsParams contains all the parameters to send to the API endpoint 57 | 58 | for the list node pools operation. 59 | 60 | Typically these are written to a http.Request. 61 | */ 62 | type ListNodePoolsParams struct { 63 | 64 | /* ClusterID. 65 | 66 | ID of the cluster. 67 | */ 68 | ClusterID string 69 | 70 | timeout time.Duration 71 | Context context.Context 72 | HTTPClient *http.Client 73 | } 74 | 75 | // WithDefaults hydrates default values in the list node pools params (not the query body). 76 | // 77 | // All values with no default are reset to their zero value. 78 | func (o *ListNodePoolsParams) WithDefaults() *ListNodePoolsParams { 79 | o.SetDefaults() 80 | return o 81 | } 82 | 83 | // SetDefaults hydrates default values in the list node pools params (not the query body). 84 | // 85 | // All values with no default are reset to their zero value. 86 | func (o *ListNodePoolsParams) SetDefaults() { 87 | // no default values defined for this parameter 88 | } 89 | 90 | // WithTimeout adds the timeout to the list node pools params 91 | func (o *ListNodePoolsParams) WithTimeout(timeout time.Duration) *ListNodePoolsParams { 92 | o.SetTimeout(timeout) 93 | return o 94 | } 95 | 96 | // SetTimeout adds the timeout to the list node pools params 97 | func (o *ListNodePoolsParams) SetTimeout(timeout time.Duration) { 98 | o.timeout = timeout 99 | } 100 | 101 | // WithContext adds the context to the list node pools params 102 | func (o *ListNodePoolsParams) WithContext(ctx context.Context) *ListNodePoolsParams { 103 | o.SetContext(ctx) 104 | return o 105 | } 106 | 107 | // SetContext adds the context to the list node pools params 108 | func (o *ListNodePoolsParams) SetContext(ctx context.Context) { 109 | o.Context = ctx 110 | } 111 | 112 | // WithHTTPClient adds the HTTPClient to the list node pools params 113 | func (o *ListNodePoolsParams) WithHTTPClient(client *http.Client) *ListNodePoolsParams { 114 | o.SetHTTPClient(client) 115 | return o 116 | } 117 | 118 | // SetHTTPClient adds the HTTPClient to the list node pools params 119 | func (o *ListNodePoolsParams) SetHTTPClient(client *http.Client) { 120 | o.HTTPClient = client 121 | } 122 | 123 | // WithClusterID adds the clusterID to the list node pools params 124 | func (o *ListNodePoolsParams) WithClusterID(clusterID string) *ListNodePoolsParams { 125 | o.SetClusterID(clusterID) 126 | return o 127 | } 128 | 129 | // SetClusterID adds the clusterId to the list node pools params 130 | func (o *ListNodePoolsParams) SetClusterID(clusterID string) { 131 | o.ClusterID = clusterID 132 | } 133 | 134 | // WriteToRequest writes these params to a swagger request 135 | func (o *ListNodePoolsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { 136 | 137 | if err := r.SetTimeout(o.timeout); err != nil { 138 | return err 139 | } 140 | var res []error 141 | 142 | // path param cluster_id 143 | if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { 144 | return err 145 | } 146 | 147 | if len(res) > 0 { 148 | return errors.CompositeValidationError(res...) 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /pkg/cluster-registry/models/config_value.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // ConfigValue config value 18 | // 19 | // swagger:model ConfigValue 20 | type ConfigValue struct { 21 | 22 | // Value of the Config value. 23 | // Example: secret-key-id 24 | // Required: true 25 | Value *string `json:"value"` 26 | } 27 | 28 | // Validate validates this config value 29 | func (m *ConfigValue) Validate(formats strfmt.Registry) error { 30 | var res []error 31 | 32 | if err := m.validateValue(formats); err != nil { 33 | res = append(res, err) 34 | } 35 | 36 | if len(res) > 0 { 37 | return errors.CompositeValidationError(res...) 38 | } 39 | return nil 40 | } 41 | 42 | func (m *ConfigValue) validateValue(formats strfmt.Registry) error { 43 | 44 | if err := validate.Required("value", "body", m.Value); err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // ContextValidate validates this config value based on context it is used 52 | func (m *ConfigValue) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 53 | return nil 54 | } 55 | 56 | // MarshalBinary interface implementation 57 | func (m *ConfigValue) MarshalBinary() ([]byte, error) { 58 | if m == nil { 59 | return nil, nil 60 | } 61 | return swag.WriteJSON(m) 62 | } 63 | 64 | // UnmarshalBinary interface implementation 65 | func (m *ConfigValue) UnmarshalBinary(b []byte) error { 66 | var res ConfigValue 67 | if err := swag.ReadJSON(b, &res); err != nil { 68 | return err 69 | } 70 | *m = res 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/cluster-registry/models/error.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/strfmt" 12 | "github.com/go-openapi/swag" 13 | ) 14 | 15 | // Error error 16 | // 17 | // swagger:model Error 18 | type Error struct { 19 | 20 | // code 21 | Code int32 `json:"code,omitempty"` 22 | 23 | // fields 24 | Fields string `json:"fields,omitempty"` 25 | 26 | // message 27 | Message string `json:"message,omitempty"` 28 | } 29 | 30 | // Validate validates this error 31 | func (m *Error) Validate(formats strfmt.Registry) error { 32 | return nil 33 | } 34 | 35 | // ContextValidate validates this error based on context it is used 36 | func (m *Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 37 | return nil 38 | } 39 | 40 | // MarshalBinary interface implementation 41 | func (m *Error) MarshalBinary() ([]byte, error) { 42 | if m == nil { 43 | return nil, nil 44 | } 45 | return swag.WriteJSON(m) 46 | } 47 | 48 | // UnmarshalBinary interface implementation 49 | func (m *Error) UnmarshalBinary(b []byte) error { 50 | var res Error 51 | if err := swag.ReadJSON(b, &res); err != nil { 52 | return err 53 | } 54 | *m = res 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /pkg/cluster-registry/models/infrastructure_account_update.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | "encoding/json" 11 | 12 | "github.com/go-openapi/errors" 13 | "github.com/go-openapi/strfmt" 14 | "github.com/go-openapi/swag" 15 | "github.com/go-openapi/validate" 16 | ) 17 | 18 | // InfrastructureAccountUpdate infrastructure account update 19 | // 20 | // swagger:model InfrastructureAccountUpdate 21 | type InfrastructureAccountUpdate struct { 22 | 23 | // Cost center of the Owner/infrastructure account 24 | // Example: 0000001234 25 | CostCenter string `json:"cost_center,omitempty"` 26 | 27 | // Level of criticality as defined by tech controlling. 1 is non 28 | // critical, 2 is standard production, 3 is PCI 29 | // 30 | // Example: 2 31 | CriticalityLevel int32 `json:"criticality_level,omitempty"` 32 | 33 | // Environment. possible values are "production" or "test". 34 | // 35 | // Example: production 36 | Environment string `json:"environment,omitempty"` 37 | 38 | // The external identifier of the account (i.e. AWS account ID) 39 | // Example: 123456789012 40 | ExternalID string `json:"external_id,omitempty"` 41 | 42 | // Globally unique ID of the infrastructure account. 43 | // Example: aws:123456789012 44 | ID string `json:"id,omitempty"` 45 | 46 | // Lifecycle Status is used to describe the current status of the account. 47 | // Enum: ["requested","creating","ready","decommissioned"] 48 | LifecycleStatus string `json:"lifecycle_status,omitempty"` 49 | 50 | // Name of the infrastructure account 51 | // Example: foo 52 | Name string `json:"name,omitempty"` 53 | 54 | // Owner of the infrastructure account (references an object in the organization service) 55 | // Example: team/bar 56 | Owner string `json:"owner,omitempty"` 57 | 58 | // Type of the infrastructure account. Possible types are "aws", "gcp", 59 | // "dc". 60 | // 61 | // Example: aws 62 | Type string `json:"type,omitempty"` 63 | } 64 | 65 | // Validate validates this infrastructure account update 66 | func (m *InfrastructureAccountUpdate) Validate(formats strfmt.Registry) error { 67 | var res []error 68 | 69 | if err := m.validateLifecycleStatus(formats); err != nil { 70 | res = append(res, err) 71 | } 72 | 73 | if len(res) > 0 { 74 | return errors.CompositeValidationError(res...) 75 | } 76 | return nil 77 | } 78 | 79 | var infrastructureAccountUpdateTypeLifecycleStatusPropEnum []interface{} 80 | 81 | func init() { 82 | var res []string 83 | if err := json.Unmarshal([]byte(`["requested","creating","ready","decommissioned"]`), &res); err != nil { 84 | panic(err) 85 | } 86 | for _, v := range res { 87 | infrastructureAccountUpdateTypeLifecycleStatusPropEnum = append(infrastructureAccountUpdateTypeLifecycleStatusPropEnum, v) 88 | } 89 | } 90 | 91 | const ( 92 | 93 | // InfrastructureAccountUpdateLifecycleStatusRequested captures enum value "requested" 94 | InfrastructureAccountUpdateLifecycleStatusRequested string = "requested" 95 | 96 | // InfrastructureAccountUpdateLifecycleStatusCreating captures enum value "creating" 97 | InfrastructureAccountUpdateLifecycleStatusCreating string = "creating" 98 | 99 | // InfrastructureAccountUpdateLifecycleStatusReady captures enum value "ready" 100 | InfrastructureAccountUpdateLifecycleStatusReady string = "ready" 101 | 102 | // InfrastructureAccountUpdateLifecycleStatusDecommissioned captures enum value "decommissioned" 103 | InfrastructureAccountUpdateLifecycleStatusDecommissioned string = "decommissioned" 104 | ) 105 | 106 | // prop value enum 107 | func (m *InfrastructureAccountUpdate) validateLifecycleStatusEnum(path, location string, value string) error { 108 | if err := validate.EnumCase(path, location, value, infrastructureAccountUpdateTypeLifecycleStatusPropEnum, true); err != nil { 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | func (m *InfrastructureAccountUpdate) validateLifecycleStatus(formats strfmt.Registry) error { 115 | if swag.IsZero(m.LifecycleStatus) { // not required 116 | return nil 117 | } 118 | 119 | // value enum 120 | if err := m.validateLifecycleStatusEnum("lifecycle_status", "body", m.LifecycleStatus); err != nil { 121 | return err 122 | } 123 | 124 | return nil 125 | } 126 | 127 | // ContextValidate validates this infrastructure account update based on context it is used 128 | func (m *InfrastructureAccountUpdate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 129 | return nil 130 | } 131 | 132 | // MarshalBinary interface implementation 133 | func (m *InfrastructureAccountUpdate) MarshalBinary() ([]byte, error) { 134 | if m == nil { 135 | return nil, nil 136 | } 137 | return swag.WriteJSON(m) 138 | } 139 | 140 | // UnmarshalBinary interface implementation 141 | func (m *InfrastructureAccountUpdate) UnmarshalBinary(b []byte) error { 142 | var res InfrastructureAccountUpdate 143 | if err := swag.ReadJSON(b, &res); err != nil { 144 | return err 145 | } 146 | *m = res 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /pkg/cluster-registry/models/node_pool.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // NodePool node pool 18 | // 19 | // swagger:model NodePool 20 | type NodePool struct { 21 | 22 | // Configuration items unique to the node pool. E.g. custom volume 23 | // configuration. 24 | // 25 | // Example: {"local_storage":"yes"} 26 | ConfigItems map[string]string `json:"config_items,omitempty"` 27 | 28 | // A discount strategy indicates the type of discount to be associated 29 | // with the node pool. This might affect the availability of the nodes in 30 | // the pools in case of preemptible or spot instances. Possible values 31 | // depend on the provider, the only common one is "none". 32 | // 33 | // Example: none 34 | // Required: true 35 | DiscountStrategy *string `json:"discount_strategy"` 36 | 37 | // instance types 38 | // Required: true 39 | // Min Items: 1 40 | // Unique: true 41 | InstanceTypes []string `json:"instance_types"` 42 | 43 | // Maximum size of the node pool 44 | // Example: 20 45 | // Required: true 46 | MaxSize *int64 `json:"max_size"` 47 | 48 | // Minimum size of the node pool 49 | // Example: 3 50 | // Required: true 51 | MinSize *int64 `json:"min_size"` 52 | 53 | // Name of the node pool 54 | // Example: pool-1 55 | // Required: true 56 | Name *string `json:"name"` 57 | 58 | // Profile used for the node pool. Possible values are "worker-default", 59 | // "worker-database", "worker-gpu", "master". The "master" profile 60 | // identifies the pool containing the cluster master 61 | // 62 | // Example: worker-default 63 | // Required: true 64 | Profile *string `json:"profile"` 65 | } 66 | 67 | // Validate validates this node pool 68 | func (m *NodePool) Validate(formats strfmt.Registry) error { 69 | var res []error 70 | 71 | if err := m.validateDiscountStrategy(formats); err != nil { 72 | res = append(res, err) 73 | } 74 | 75 | if err := m.validateInstanceTypes(formats); err != nil { 76 | res = append(res, err) 77 | } 78 | 79 | if err := m.validateMaxSize(formats); err != nil { 80 | res = append(res, err) 81 | } 82 | 83 | if err := m.validateMinSize(formats); err != nil { 84 | res = append(res, err) 85 | } 86 | 87 | if err := m.validateName(formats); err != nil { 88 | res = append(res, err) 89 | } 90 | 91 | if err := m.validateProfile(formats); err != nil { 92 | res = append(res, err) 93 | } 94 | 95 | if len(res) > 0 { 96 | return errors.CompositeValidationError(res...) 97 | } 98 | return nil 99 | } 100 | 101 | func (m *NodePool) validateDiscountStrategy(formats strfmt.Registry) error { 102 | 103 | if err := validate.Required("discount_strategy", "body", m.DiscountStrategy); err != nil { 104 | return err 105 | } 106 | 107 | return nil 108 | } 109 | 110 | func (m *NodePool) validateInstanceTypes(formats strfmt.Registry) error { 111 | 112 | if err := validate.Required("instance_types", "body", m.InstanceTypes); err != nil { 113 | return err 114 | } 115 | 116 | iInstanceTypesSize := int64(len(m.InstanceTypes)) 117 | 118 | if err := validate.MinItems("instance_types", "body", iInstanceTypesSize, 1); err != nil { 119 | return err 120 | } 121 | 122 | if err := validate.UniqueItems("instance_types", "body", m.InstanceTypes); err != nil { 123 | return err 124 | } 125 | 126 | return nil 127 | } 128 | 129 | func (m *NodePool) validateMaxSize(formats strfmt.Registry) error { 130 | 131 | if err := validate.Required("max_size", "body", m.MaxSize); err != nil { 132 | return err 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func (m *NodePool) validateMinSize(formats strfmt.Registry) error { 139 | 140 | if err := validate.Required("min_size", "body", m.MinSize); err != nil { 141 | return err 142 | } 143 | 144 | return nil 145 | } 146 | 147 | func (m *NodePool) validateName(formats strfmt.Registry) error { 148 | 149 | if err := validate.Required("name", "body", m.Name); err != nil { 150 | return err 151 | } 152 | 153 | return nil 154 | } 155 | 156 | func (m *NodePool) validateProfile(formats strfmt.Registry) error { 157 | 158 | if err := validate.Required("profile", "body", m.Profile); err != nil { 159 | return err 160 | } 161 | 162 | return nil 163 | } 164 | 165 | // ContextValidate validates this node pool based on context it is used 166 | func (m *NodePool) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 167 | return nil 168 | } 169 | 170 | // MarshalBinary interface implementation 171 | func (m *NodePool) MarshalBinary() ([]byte, error) { 172 | if m == nil { 173 | return nil, nil 174 | } 175 | return swag.WriteJSON(m) 176 | } 177 | 178 | // UnmarshalBinary interface implementation 179 | func (m *NodePool) UnmarshalBinary(b []byte) error { 180 | var res NodePool 181 | if err := swag.ReadJSON(b, &res); err != nil { 182 | return err 183 | } 184 | *m = res 185 | return nil 186 | } 187 | -------------------------------------------------------------------------------- /pkg/cluster-registry/models/node_pool_update.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-swagger; DO NOT EDIT. 2 | 3 | package models 4 | 5 | // This file was generated by the swagger tool. 6 | // Editing this file might prove futile when you re-run the swagger generate command 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/go-openapi/errors" 12 | "github.com/go-openapi/strfmt" 13 | "github.com/go-openapi/swag" 14 | "github.com/go-openapi/validate" 15 | ) 16 | 17 | // NodePoolUpdate node pool update 18 | // 19 | // swagger:model NodePoolUpdate 20 | type NodePoolUpdate struct { 21 | 22 | // Configuration items unique to the node pool. E.g. custom volume 23 | // configuration. 24 | // 25 | // Example: {"local_storage":"yes"} 26 | ConfigItems map[string]string `json:"config_items,omitempty"` 27 | 28 | // A discount strategy indicates the type of discount to be associated 29 | // with the node pool. This might affect the availability of the nodes in 30 | // the pools in case of preemptible or spot instances. Possible values 31 | // depend on the provider, the only common one is "none". 32 | // 33 | // Example: none 34 | DiscountStrategy string `json:"discount_strategy,omitempty"` 35 | 36 | // instance types 37 | // Min Items: 1 38 | // Unique: true 39 | InstanceTypes []string `json:"instance_types"` 40 | 41 | // Maximum size of the node pool 42 | // Example: 20 43 | MaxSize int64 `json:"max_size,omitempty"` 44 | 45 | // Minimum size of the node pool 46 | // Example: 3 47 | MinSize int64 `json:"min_size,omitempty"` 48 | 49 | // Name of the node pool 50 | // Example: pool-1 51 | Name string `json:"name,omitempty"` 52 | 53 | // Profile used for the node pool. Possible values are "worker-default", 54 | // "worker-database", "worker-gpu", "master". The "master" profile 55 | // identifies the pool containing the cluster master 56 | // 57 | // Example: worker-default 58 | Profile string `json:"profile,omitempty"` 59 | } 60 | 61 | // Validate validates this node pool update 62 | func (m *NodePoolUpdate) Validate(formats strfmt.Registry) error { 63 | var res []error 64 | 65 | if err := m.validateInstanceTypes(formats); err != nil { 66 | res = append(res, err) 67 | } 68 | 69 | if len(res) > 0 { 70 | return errors.CompositeValidationError(res...) 71 | } 72 | return nil 73 | } 74 | 75 | func (m *NodePoolUpdate) validateInstanceTypes(formats strfmt.Registry) error { 76 | if swag.IsZero(m.InstanceTypes) { // not required 77 | return nil 78 | } 79 | 80 | iInstanceTypesSize := int64(len(m.InstanceTypes)) 81 | 82 | if err := validate.MinItems("instance_types", "body", iInstanceTypesSize, 1); err != nil { 83 | return err 84 | } 85 | 86 | if err := validate.UniqueItems("instance_types", "body", m.InstanceTypes); err != nil { 87 | return err 88 | } 89 | 90 | return nil 91 | } 92 | 93 | // ContextValidate validates this node pool update based on context it is used 94 | func (m *NodePoolUpdate) ContextValidate(ctx context.Context, formats strfmt.Registry) error { 95 | return nil 96 | } 97 | 98 | // MarshalBinary interface implementation 99 | func (m *NodePoolUpdate) MarshalBinary() ([]byte, error) { 100 | if m == nil { 101 | return nil, nil 102 | } 103 | return swag.WriteJSON(m) 104 | } 105 | 106 | // UnmarshalBinary interface implementation 107 | func (m *NodePoolUpdate) UnmarshalBinary(b []byte) error { 108 | var res NodePoolUpdate 109 | if err := swag.ReadJSON(b, &res); err != nil { 110 | return err 111 | } 112 | *m = res 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/credentials-loader/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | jwt "github.com/golang-jwt/jwt" 9 | ) 10 | 11 | // Claims defines the claims of a jwt token. 12 | type Claims struct { 13 | Sub string `json:"sub"` 14 | Realm string `json:"https://identity.zalando.com/realm"` 15 | Type string `json:"https://identity.zalando.com/token"` 16 | ManagedID string `json:"https://identity.zalando.com/managed-id"` 17 | Azp string `json:"azp"` 18 | Businesspartnet string `json:"https://identity.zalando.com/bp"` 19 | AuthTime int `json:"auth_time"` 20 | ISS string `json:"iss"` 21 | Exp int `json:"exp"` 22 | IAt int `json:"iat"` 23 | } 24 | 25 | // UID returns the claim mapping to the uid depending on type. 26 | func (c *Claims) UID() string { 27 | switch c.Realm { 28 | case "users": 29 | return c.ManagedID 30 | default: 31 | return c.Sub 32 | } 33 | } 34 | 35 | // ParseClaims parses the claims of a jwt token. 36 | func ParseClaims(token string) (*Claims, error) { 37 | parts := strings.Split(token, ".") 38 | if len(parts) != 3 { 39 | return nil, fmt.Errorf("token contains an invalid number of segments") 40 | } 41 | 42 | d, err := jwt.DecodeSegment(parts[1]) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | var claims Claims 48 | err = json.Unmarshal(d, &claims) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return &claims, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/credentials-loader/jwt/jwt_test.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | const ( 10 | token = "HEADER.eyJzdWIiOiJpZCIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vcmVhbG0iOiJ1c2VycyIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vdG9rZW4iOiJCZWFyZXIiLCJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tL21hbmFnZWQtaWQiOiJ1c2VyIiwiYXpwIjoiZ28tenRva2VuIiwiaHR0cHM6Ly9pZGVudGl0eS56YWxhbmRvLmNvbS9icCI6ImJwIiwiYXV0aF90aW1lIjoxNDk4MzE3MjIzLCJpc3MiOiJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tIiwiZXhwIjoxNDk5MjM1OTgzLCJpYXQiOjE0OTkyMzIzNzN9.SIGNATURE" 11 | invalidPayload = "HEADER.eyJzdWIiOiJpZCIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vcmVhbG0iOiJ1c2VycyIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vdG9rZW4iOiJCZWFyZXIiLCJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tL21hbmFnZWQtaWQiOiJ1c2VyIiwiYXpwIjoiZ28tenRva2VuIiwiaHR0cHM6Ly9pZGVudGl0eS56YWxhbmRvLmNvbS9icCI6ImJwIiwiYXV0aF90aW1lIjoxNDk4MzE3MjIzLCJpc3MiOiJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tIiwiZXhwIjoxNDk5MjM1OTgzLCJpYXQiOjE0OTkyMzIzNzM=.SIGNATURE" 12 | invalidPayloadSegment = "HEADER.|eyJzdWIiOiJpZCIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vcmVhbG0iOiJ1c2VycyIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vdG9rZW4iOiJCZWFyZXIiLCJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tL21hbmFnZWQtaWQiOiJ1c2VyIiwiYXpwIjoiZ28tenRva2VuIiwiaHR0cHM6Ly9pZGVudGl0eS56YWxhbmRvLmNvbS9icCI6ImJwIiwiYXV0aF90aW1lIjoxNDk4MzE3MjIzLCJpc3MiOiJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tIiwiZXhwIjoxNDk5MjM1OTgzLCJpYXQiOjE0OTkyMzIzNzN9.SIGNATURE" 13 | invalidSegments = "HEADER.SIGNATURE" 14 | ) 15 | 16 | func TestParseClaims(t *testing.T) { 17 | c, err := ParseClaims(token) 18 | assert.NoError(t, err) 19 | assert.Equal(t, c.ManagedID, "user") 20 | 21 | _, err = ParseClaims(invalidPayload) 22 | assert.Error(t, err) 23 | 24 | _, err = ParseClaims(invalidSegments) 25 | assert.Error(t, err) 26 | 27 | _, err = ParseClaims(invalidPayloadSegment) 28 | assert.Error(t, err) 29 | } 30 | 31 | func TestClaimsUID(t *testing.T) { 32 | claims := &Claims{Realm: "users", Sub: "service", ManagedID: "user"} 33 | s := claims.UID() 34 | assert.Equal(t, claims.ManagedID, s) 35 | 36 | claims.Realm = "services" 37 | s = claims.UID() 38 | assert.Equal(t, claims.Sub, s) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/credentials-loader/platformiam/file.go: -------------------------------------------------------------------------------- 1 | package platformiam 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "time" 8 | 9 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/credentials-loader/jwt" 10 | "golang.org/x/oauth2" 11 | ) 12 | 13 | // NewTokenSource returns a platformIAMTokenSource 14 | func NewTokenSource(name string, path string) oauth2.TokenSource { 15 | return &tokenSource{ 16 | name: name, 17 | path: path, 18 | } 19 | } 20 | 21 | // NewClientCredentialsSource returns a NewPlatformIAMClientCredentialsSource 22 | func NewClientCredentialsSource(name string, path string) ClientCredentialsSource { 23 | return &clientCredentialsSource{ 24 | name: name, 25 | path: path, 26 | } 27 | } 28 | 29 | // ClientCredentialsSource is an interface that allows access to client credentials 30 | type ClientCredentialsSource interface { 31 | ClientCredentials() (*ClientCredentials, error) 32 | } 33 | 34 | type source struct { 35 | name string 36 | path string 37 | } 38 | 39 | // tokenSource defines a source that contains a token 40 | type tokenSource source 41 | 42 | // clientCredentialsSource defines a source that contains client credentials 43 | type clientCredentialsSource source 44 | 45 | func readFileContent(path string) (string, error) { 46 | data, err := os.ReadFile(path) 47 | if err != nil { 48 | return "", err 49 | } 50 | return string(data), nil 51 | } 52 | 53 | // Token returns a token or an error. 54 | // Token must be safe for concurrent use by multiple goroutines. 55 | // The returned Token must not be modified. 56 | func (p *tokenSource) Token() (*oauth2.Token, error) { 57 | token, err := readFileContent(path.Join(p.path, fmt.Sprintf("%s-token-secret", p.name))) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | tokenType, err := readFileContent(path.Join(p.path, fmt.Sprintf("%s-token-type", p.name))) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | // parse the token claims to get expiry time 68 | claims, err := jwt.ParseClaims(token) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return &oauth2.Token{ 74 | AccessToken: token, 75 | TokenType: tokenType, 76 | Expiry: time.Unix(int64(claims.Exp), 0), 77 | }, nil 78 | } 79 | 80 | // ClientCredentials contains ID and Secret to use to authenticate 81 | type ClientCredentials struct { 82 | ID string 83 | Secret string 84 | } 85 | 86 | // ClientCredentials returns a pointer to a ClientCredentials object or an error 87 | func (p *clientCredentialsSource) ClientCredentials() (*ClientCredentials, error) { 88 | id, err := readFileContent(path.Join(p.path, fmt.Sprintf("%s-client-id", p.name))) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | secret, err := readFileContent(path.Join(p.path, fmt.Sprintf("%s-client-secret", p.name))) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | return &ClientCredentials{ 99 | ID: id, 100 | Secret: secret, 101 | }, nil 102 | } 103 | -------------------------------------------------------------------------------- /pkg/credentials-loader/platformiam/file_test.go: -------------------------------------------------------------------------------- 1 | package platformiam 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const ( 10 | testToken = "HEADER.eyJzdWIiOiJpZCIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vcmVhbG0iOiJ1c2VycyIsImh0dHBzOi8vaWRlbnRpdHkuemFsYW5kby5jb20vdG9rZW4iOiJCZWFyZXIiLCJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tL21hbmFnZWQtaWQiOiJ1c2VyIiwiYXpwIjoiZ28tenRva2VuIiwiaHR0cHM6Ly9pZGVudGl0eS56YWxhbmRvLmNvbS9icCI6ImJwIiwiYXV0aF90aW1lIjoxNDk4MzE3MjIzLCJpc3MiOiJodHRwczovL2lkZW50aXR5LnphbGFuZG8uY29tIiwiZXhwIjoxNDk5MjM1OTgzLCJpYXQiOjE0OTkyMzIzNzN9.SIGNATURE" 11 | ) 12 | 13 | var ( 14 | // expiry time in token 15 | expiryTime = time.Date(2017, 07, 05, 06, 26, 23, 0, time.UTC) 16 | ) 17 | 18 | func CreateTempTokenFiles() { 19 | _ = os.WriteFile("foo-token-type", []byte("Bearer"), 0644) 20 | _ = os.WriteFile("foo-token-secret", []byte(testToken), 0644) 21 | } 22 | 23 | func CreateTempClientCredentialsFiles() { 24 | _ = os.WriteFile("bar-client-secret", []byte("HksjnJHhhjshd"), 0644) 25 | _ = os.WriteFile("bar-client-id", []byte("foo-bar"), 0644) 26 | } 27 | 28 | func DeleteTempTokenFiles() { 29 | _ = os.Remove("foo-token-type") 30 | _ = os.Remove("foo-token-secret") 31 | } 32 | 33 | func DeleteTempClientCredentialsFiles() { 34 | _ = os.Remove("bar-client-secret") 35 | _ = os.Remove("bar-client-id") 36 | } 37 | 38 | func TestToken(t *testing.T) { 39 | CreateTempTokenFiles() 40 | defer DeleteTempTokenFiles() 41 | 42 | p := NewTokenSource("foo", ".") 43 | token, err := p.Token() 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | t.Logf("Token: %s, type: %s, expiry: %s", token.AccessToken, token.TokenType, token.Expiry) 48 | if token.AccessToken != testToken && token.TokenType != "Bearer" && token.Expiry != expiryTime { 49 | t.Errorf("Error, expecting token: %s, got: %s and type: %s, got: %s", "token.foo.bar", token.AccessToken, "Bearer", token.TokenType) 50 | } 51 | } 52 | 53 | func TestClientCredentials(t *testing.T) { 54 | CreateTempClientCredentialsFiles() 55 | defer DeleteTempClientCredentialsFiles() 56 | 57 | p := NewClientCredentialsSource("bar", ".") 58 | cred, err := p.ClientCredentials() 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | t.Logf("Client ID: %s, Client Secret: %s", cred.ID, cred.Secret) 63 | if cred.ID != "foo-bar" && cred.Secret != "HksjnJHhhjshd" { 64 | t.Errorf("Error, expecting ID: %s, got: %s and Secret: %s, got: %s", "foo-bar", cred.ID, "HksjnJHhhjshd", cred.Secret) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pkg/decrypter/decrypter.go: -------------------------------------------------------------------------------- 1 | package decrypter 2 | 3 | import "strings" 4 | 5 | // SecretDecrypter is a map of decrypters. 6 | type SecretDecrypter map[string]Decrypter 7 | 8 | // Decrypt tries to find the right decrypter for the secret based on the secret 9 | // prefix e.g. 'aws:kms:'. If the decrypter is found it will attempt to decrypt 10 | // the secret and return it in plaintext. 11 | func (s SecretDecrypter) Decrypt(secret string) (string, error) { 12 | if strings.HasPrefix(secret, AWSKMSSecretPrefix) { 13 | if decrypter, ok := s[AWSKMSSecretPrefix]; ok { 14 | return decrypter.Decrypt(strings.TrimPrefix(secret, AWSKMSSecretPrefix)) 15 | } 16 | } 17 | 18 | // in case no decrypter is found we just return the secret as 19 | // 'plaintext' 20 | return secret, nil 21 | } 22 | 23 | // Decrypter is an interface describing any type of secret decrypter which 24 | // given an ecrypted secret can return the decrypted plaintext value. 25 | type Decrypter interface { 26 | Decrypt(secret string) (string, error) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/decrypter/decrypter_test.go: -------------------------------------------------------------------------------- 1 | package decrypter 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | type mockDecrypter struct{} 9 | 10 | func (d mockDecrypter) Decrypt(_ string) (string, error) { 11 | return "", nil 12 | } 13 | 14 | type mockErrDecrypter struct{} 15 | 16 | func (d mockErrDecrypter) Decrypt(_ string) (string, error) { 17 | return "", fmt.Errorf("failed") 18 | } 19 | 20 | func TestSecretDecrypterDecrypt(t *testing.T) { 21 | for _, ti := range []struct { 22 | msg string 23 | decrypter SecretDecrypter 24 | secret string 25 | success bool 26 | }{ 27 | { 28 | msg: "test successful decryption", 29 | decrypter: SecretDecrypter(map[string]Decrypter{ 30 | AWSKMSSecretPrefix: mockDecrypter{}, 31 | }), 32 | secret: AWSKMSSecretPrefix + "my-secret", 33 | success: true, 34 | }, 35 | { 36 | msg: "test returning the secret without decrypting", 37 | decrypter: SecretDecrypter(map[string]Decrypter{ 38 | "custom:": mockDecrypter{}, 39 | }), 40 | secret: "custom:my-secret", 41 | success: true, 42 | }, 43 | { 44 | msg: "test when decrypt failes", 45 | decrypter: SecretDecrypter(map[string]Decrypter{ 46 | AWSKMSSecretPrefix: mockErrDecrypter{}, 47 | }), 48 | secret: AWSKMSSecretPrefix + "my-secret", 49 | success: false, 50 | }, 51 | } { 52 | t.Run(ti.msg, func(t *testing.T) { 53 | _, err := ti.decrypter.Decrypt(ti.secret) 54 | if err != nil && ti.success { 55 | t.Errorf("should not fail: %s", err) 56 | } 57 | 58 | if err == nil && !ti.success { 59 | t.Errorf("expected error") 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pkg/decrypter/kms.go: -------------------------------------------------------------------------------- 1 | package decrypter 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/aws/aws-sdk-go/service/kms" 8 | ) 9 | 10 | const ( 11 | // AWSKMSSecretPrefix is the secret prefix for secrets which can be 12 | // descrypted by the awsKMS decrypter. 13 | AWSKMSSecretPrefix = "aws:kms:" 14 | ) 15 | 16 | // awsKMS is a decrypter which can decrypt kms encrypted secrets. 17 | type awsKMS struct { 18 | kmsSvc *kms.KMS 19 | } 20 | 21 | // NewAWSKMSDescrypter initializes a new awsKMS based Decrypter. 22 | func NewAWSKMSDescrypter(sess *session.Session) Decrypter { 23 | return &awsKMS{ 24 | kmsSvc: kms.New(sess), 25 | } 26 | } 27 | 28 | // Decrypt decrypts a kms encrypted secret. 29 | func (a *awsKMS) Decrypt(secret string) (string, error) { 30 | decodedSecret, err := base64.StdEncoding.DecodeString(secret) 31 | if err != nil { 32 | return "", err 33 | } 34 | 35 | params := &kms.DecryptInput{ 36 | CiphertextBlob: decodedSecret, 37 | } 38 | 39 | resp, err := a.kmsSvc.Decrypt(params) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | return string(resp.Plaintext), nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/updatestrategy/clc_update.go: -------------------------------------------------------------------------------- 1 | package updatestrategy 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/cenkalti/backoff" 8 | log "github.com/sirupsen/logrus" 9 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 10 | ) 11 | 12 | type CLCUpdateStrategy struct { 13 | nodePoolManager NodePoolManager 14 | logger *log.Entry 15 | pollingInterval time.Duration 16 | } 17 | 18 | // NewCLCUpdateStrategy initializes a new CLCUpdateStrategy. 19 | func NewCLCUpdateStrategy(logger *log.Entry, nodePoolManager NodePoolManager, pollingInterval time.Duration) *CLCUpdateStrategy { 20 | return &CLCUpdateStrategy{ 21 | nodePoolManager: nodePoolManager, 22 | logger: logger.WithField("strategy", "clc"), 23 | pollingInterval: pollingInterval, 24 | } 25 | } 26 | 27 | func (c *CLCUpdateStrategy) Update(ctx context.Context, nodePoolDesc *api.NodePool) error { 28 | c.logger.Infof("Initializing update of node pool '%s'", nodePoolDesc.Name) 29 | 30 | err := c.doUpdate(ctx, nodePoolDesc) 31 | if err != nil { 32 | if ctx.Err() == context.Canceled { 33 | backoffCfg := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 10) 34 | err := backoff.Retry(func() error { 35 | return c.rollbackUpdate(context.Background(), nodePoolDesc) 36 | }, backoffCfg) 37 | if err != nil { 38 | c.logger.Errorf("Error while aborting the update for node pool '%s': %v", nodePoolDesc.Name, err) 39 | } 40 | } 41 | return err 42 | } 43 | 44 | c.logger.Infof("Node pool '%s' successfully updated", nodePoolDesc.Name) 45 | return nil 46 | } 47 | 48 | func (c *CLCUpdateStrategy) PrepareForRemoval(ctx context.Context, nodePoolDesc *api.NodePool) error { 49 | c.logger.Infof("Preparing for removal of node pool '%s'", nodePoolDesc.Name) 50 | 51 | for { 52 | nodePool, err := c.nodePoolManager.GetPool(ctx, nodePoolDesc) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | err = c.nodePoolManager.MarkPoolForDecommission(ctx, nodePoolDesc) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | for _, node := range nodePool.Nodes { 63 | err := c.nodePoolManager.DisableReplacementNodeProvisioning(ctx, node) 64 | if err != nil { 65 | return err 66 | } 67 | } 68 | 69 | nodes, err := c.markNodes(ctx, nodePool, func(_ *Node) bool { 70 | return true 71 | }) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | if nodes == 0 { 77 | return nil 78 | } 79 | 80 | c.logger.WithField("node-pool", nodePoolDesc.Name).Infof("Waiting for decommissioning of the nodes (%d left)", nodes) 81 | 82 | // wait for CLC to finish removing the nodes 83 | select { 84 | case <-ctx.Done(): 85 | return ctx.Err() 86 | case <-time.After(c.pollingInterval): 87 | } 88 | } 89 | } 90 | 91 | func (c *CLCUpdateStrategy) rollbackUpdate(ctx context.Context, nodePoolDesc *api.NodePool) error { 92 | nodePool, err := c.nodePoolManager.GetPool(ctx, nodePoolDesc) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | for _, node := range nodePool.Nodes { 98 | if node.Generation != nodePool.Generation { 99 | err := c.nodePoolManager.AbortNodeDecommissioning(ctx, node) 100 | if err != nil { 101 | return err 102 | } 103 | } 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func (c *CLCUpdateStrategy) doUpdate(ctx context.Context, nodePoolDesc *api.NodePool) error { 110 | for { 111 | if err := ctx.Err(); err != nil { 112 | return err 113 | } 114 | 115 | nodePool, err := c.nodePoolManager.GetPool(ctx, nodePoolDesc) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | oldNodes, err := c.markNodes(ctx, nodePool, func(node *Node) bool { 121 | return node.Generation != nodePool.Generation 122 | }) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | if oldNodes == 0 { 128 | return nil 129 | } 130 | 131 | c.logger.WithField("node-pool", nodePoolDesc.Name).Infof("Waiting for decommissioning of old nodes (%d left)", oldNodes) 132 | 133 | // wait for CLC to finish rolling the nodes 134 | select { 135 | case <-ctx.Done(): 136 | return ctx.Err() 137 | case <-time.After(c.pollingInterval): 138 | } 139 | } 140 | } 141 | 142 | func (c *CLCUpdateStrategy) markNodes(ctx context.Context, nodePool *NodePool, predicate func(*Node) bool) (int, error) { 143 | marked := 0 144 | for _, node := range nodePool.Nodes { 145 | if predicate(node) { 146 | err := c.nodePoolManager.MarkNodeForDecommission(ctx, node) 147 | marked++ 148 | if err != nil { 149 | return 0, err 150 | } 151 | } 152 | } 153 | return marked, nil 154 | } 155 | -------------------------------------------------------------------------------- /pkg/updatestrategy/updatestrategy.go: -------------------------------------------------------------------------------- 1 | package updatestrategy 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 7 | v1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | // UpdateStrategy defines an interface for performing cluster node updates. 11 | type UpdateStrategy interface { 12 | Update(ctx context.Context, nodePool *api.NodePool) error 13 | PrepareForRemoval(ctx context.Context, nodePool *api.NodePool) error 14 | } 15 | 16 | // ProviderNodePoolsBackend is an interface for describing a node pools 17 | // provider backend e.g. AWS Auto Scaling Groups. 18 | type ProviderNodePoolsBackend interface { 19 | Get(ctx context.Context, nodePool *api.NodePool) (*NodePool, error) 20 | Scale(ctx context.Context, nodePool *api.NodePool, replicas int) error 21 | MarkForDecommission(ctx context.Context, nodePool *api.NodePool) error 22 | Terminate(ctx context.Context, nodePool *api.NodePool, node *Node, decrementDesired bool) error 23 | } 24 | 25 | // NodePool defines a node pool including all nodes. 26 | type NodePool struct { 27 | Min int 28 | Desired int 29 | Current int 30 | Max int 31 | Generation int 32 | Nodes []*Node 33 | } 34 | 35 | // ReadyNodes returns a list of nodes which are marked as ready. 36 | func (n *NodePool) ReadyNodes() []*Node { 37 | nodes := make([]*Node, 0, len(n.Nodes)) 38 | for _, node := range n.Nodes { 39 | if node.Ready { 40 | nodes = append(nodes, node) 41 | } 42 | } 43 | 44 | return nodes 45 | } 46 | 47 | // Node is an abstract node object which combines the node information from the 48 | // node pool backend along with the corresponding Kubernetes node object. 49 | type Node struct { 50 | Name string 51 | Annotations map[string]string 52 | Labels map[string]string 53 | Taints []v1.Taint 54 | Cordoned bool 55 | ProviderID string 56 | FailureDomain string 57 | Generation int 58 | VolumesAttached bool 59 | Ready bool 60 | Master bool 61 | } 62 | 63 | // ProfileNodePoolProvisioner is a NodePoolProvisioner which selects the 64 | // backend provisioner based on the node pool profile. It has a default 65 | // provisioner and a mapping of profile to provisioner for those profiles which 66 | // can't use the default provisioner. 67 | type ProfileNodePoolProvisioner struct { 68 | defaultProvisioner ProviderNodePoolsBackend 69 | profileMapping map[string]ProviderNodePoolsBackend 70 | } 71 | 72 | // NewProfileNodePoolsBackend initializes a new ProfileNodePoolProvisioner. 73 | func NewProfileNodePoolsBackend(defaultProvisioner ProviderNodePoolsBackend, profileMapping map[string]ProviderNodePoolsBackend) *ProfileNodePoolProvisioner { 74 | return &ProfileNodePoolProvisioner{ 75 | defaultProvisioner: defaultProvisioner, 76 | profileMapping: profileMapping, 77 | } 78 | } 79 | 80 | // Get the specified node pool using the right node pool provisioner for the 81 | // profile. 82 | func (n *ProfileNodePoolProvisioner) Get(ctx context.Context, nodePool *api.NodePool) (*NodePool, error) { 83 | if provisioner, ok := n.profileMapping[nodePool.Profile]; ok { 84 | return provisioner.Get(ctx, nodePool) 85 | } 86 | 87 | return n.defaultProvisioner.Get(ctx, nodePool) 88 | } 89 | 90 | // MarkForDecommission marks a node pool for decommissioning using the right 91 | // node pool provisioner for the profile. 92 | func (n *ProfileNodePoolProvisioner) MarkForDecommission(ctx context.Context, nodePool *api.NodePool) error { 93 | if provisioner, ok := n.profileMapping[nodePool.Profile]; ok { 94 | return provisioner.MarkForDecommission(ctx, nodePool) 95 | } 96 | 97 | return n.defaultProvisioner.MarkForDecommission(ctx, nodePool) 98 | } 99 | 100 | // Scale scales a node pool using the right node pool provisioner for the 101 | // profile. 102 | func (n *ProfileNodePoolProvisioner) Scale(ctx context.Context, nodePool *api.NodePool, replicas int) error { 103 | if provisioner, ok := n.profileMapping[nodePool.Profile]; ok { 104 | return provisioner.Scale(ctx, nodePool, replicas) 105 | } 106 | 107 | return n.defaultProvisioner.Scale(ctx, nodePool, replicas) 108 | } 109 | 110 | // Terminate terminates a node pool using the default provisioner. 111 | func (n *ProfileNodePoolProvisioner) Terminate(ctx context.Context, nodePool *api.NodePool, node *Node, decrementDesired bool) error { 112 | if provisioner, ok := n.profileMapping[nodePool.Profile]; ok { 113 | return provisioner.Terminate(ctx, nodePool, node, decrementDesired) 114 | } 115 | 116 | return n.defaultProvisioner.Terminate(ctx, nodePool, node, decrementDesired) 117 | } 118 | -------------------------------------------------------------------------------- /pkg/updatestrategy/updatestrategy_test.go: -------------------------------------------------------------------------------- 1 | package updatestrategy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestReadyNodes(t *testing.T) { 10 | nodePool := &NodePool{ 11 | Nodes: []*Node{ 12 | { 13 | Ready: true, 14 | }, 15 | }, 16 | } 17 | 18 | nodes := nodePool.ReadyNodes() 19 | assert.Len(t, nodes, 1) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/util/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "io" 8 | "os/exec" 9 | "strings" 10 | 11 | log "github.com/sirupsen/logrus" 12 | "golang.org/x/sync/semaphore" 13 | ) 14 | 15 | // ExecManager limits the number of external commands that are running at the same time 16 | type ExecManager struct { 17 | sema *semaphore.Weighted 18 | } 19 | 20 | func NewExecManager(maxConcurrency uint) *ExecManager { 21 | return &ExecManager{ 22 | sema: semaphore.NewWeighted(int64(maxConcurrency)), 23 | } 24 | } 25 | 26 | func outputLines(output string) []string { 27 | return strings.Split(strings.TrimRight(output, "\n"), "\n") 28 | } 29 | 30 | // RunSilently runs an exec.Cmd, capturing its output and additionally logging it 31 | // only if the command fails (or if debug logging is enabled) 32 | func (m *ExecManager) RunSilently(ctx context.Context, logger *log.Entry, cmd *exec.Cmd) (string, error) { 33 | err := m.sema.Acquire(ctx, 1) 34 | if err != nil { 35 | return "", err 36 | } 37 | defer m.sema.Release(1) 38 | 39 | rawOut, err := cmd.CombinedOutput() 40 | out := string(rawOut) 41 | if err != nil { 42 | for _, line := range outputLines(out) { 43 | logger.Errorln(line) 44 | } 45 | } else if logger.Logger.Level >= log.DebugLevel { 46 | for _, line := range outputLines(out) { 47 | logger.Debugln(line) 48 | } 49 | } 50 | return string(out), err 51 | } 52 | 53 | // entry.WriterLevel for some reason creates a pipes and a goroutine which makes it impossible to test without hacks 54 | type logWriter func(args ...interface{}) 55 | 56 | func (w logWriter) Write(p []byte) (n int, err error) { 57 | scanner := bufio.NewScanner(bytes.NewReader(p)) 58 | scanner.Split(bufio.ScanLines) 59 | for scanner.Scan() { 60 | w(scanner.Text()) 61 | } 62 | return len(p), nil 63 | } 64 | 65 | // Run runs an exec.Cmd, capturing its output and additionally redirecting 66 | // it to a logger 67 | func (m *ExecManager) Run(ctx context.Context, logger *log.Entry, cmd *exec.Cmd) (string, error) { 68 | err := m.sema.Acquire(ctx, 1) 69 | if err != nil { 70 | return "", err 71 | } 72 | defer m.sema.Release(1) 73 | 74 | var output bytes.Buffer 75 | 76 | cmd.Stdout = io.MultiWriter(&output, logWriter(logger.Info)) 77 | cmd.Stderr = io.MultiWriter(&output, logWriter(logger.Error)) 78 | 79 | err = cmd.Run() 80 | return output.String(), err 81 | } 82 | -------------------------------------------------------------------------------- /pkg/util/command/command_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os/exec" 7 | "sync" 8 | "testing" 9 | 10 | log "github.com/sirupsen/logrus" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | type syncWriter struct { 15 | sync.Mutex 16 | buf bytes.Buffer 17 | } 18 | 19 | type testLogger struct { 20 | writer *syncWriter 21 | entry *log.Entry 22 | } 23 | 24 | var ( 25 | testManager = NewExecManager(1) 26 | ) 27 | 28 | func (w *syncWriter) Write(p []byte) (int, error) { 29 | w.Lock() 30 | n, err := w.buf.Write(p) 31 | w.Unlock() 32 | return n, err 33 | } 34 | 35 | func (w *syncWriter) String() string { 36 | w.Lock() 37 | res := w.buf.String() 38 | w.Unlock() 39 | return res 40 | } 41 | 42 | func newTestLogger(level log.Level) *testLogger { 43 | buf := &syncWriter{} 44 | 45 | logger := log.New() 46 | logger.Out = buf 47 | logger.Level = level 48 | 49 | return &testLogger{ 50 | writer: buf, 51 | entry: log.NewEntry(logger), 52 | } 53 | } 54 | 55 | func TestRunSilentlySuccessful(t *testing.T) { 56 | cmd := exec.Command("echo", "foo") 57 | logger := newTestLogger(log.InfoLevel) 58 | out, err := testManager.RunSilently(context.Background(), logger.entry, cmd) 59 | require.NoError(t, err) 60 | require.Contains(t, out, "foo") 61 | require.Empty(t, logger.writer.String()) 62 | } 63 | 64 | func TestRunSilentlyDebug(t *testing.T) { 65 | cmd := exec.Command("echo", "foo") 66 | logger := newTestLogger(log.DebugLevel) 67 | out, err := testManager.RunSilently(context.Background(), logger.entry, cmd) 68 | require.NoError(t, err) 69 | require.Contains(t, out, "foo") 70 | require.NotEmpty(t, logger.writer.String()) 71 | } 72 | 73 | func TestRunSilentlyFailing(t *testing.T) { 74 | cmd := exec.Command("sh", "-c", "echo foo && false") 75 | logger := newTestLogger(log.InfoLevel) 76 | out, err := testManager.RunSilently(context.Background(), logger.entry, cmd) 77 | require.Error(t, err) 78 | require.Contains(t, out, "foo") 79 | require.NotEmpty(t, logger.writer.String()) 80 | } 81 | 82 | func TestRunSuccessful(t *testing.T) { 83 | cmd := exec.Command("sh", "-c", "echo foo && false") 84 | logger := newTestLogger(log.InfoLevel) 85 | out, err := testManager.Run(context.Background(), logger.entry, cmd) 86 | require.Error(t, err) 87 | require.Contains(t, out, "foo") 88 | require.NotEmpty(t, logger.writer.String()) 89 | } 90 | 91 | func TestRunFailing(t *testing.T) { 92 | cmd := exec.Command("echo", "foo") 93 | logger := newTestLogger(log.InfoLevel) 94 | out, err := testManager.Run(context.Background(), logger.entry, cmd) 95 | require.NoError(t, err) 96 | require.Contains(t, out, "foo") 97 | require.NotEmpty(t, logger.writer.String()) 98 | } 99 | -------------------------------------------------------------------------------- /pkg/util/copy.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func CopyValues(value map[string]interface{}) map[string]interface{} { 4 | result := make(map[string]interface{}, len(value)) 5 | for k, v := range value { 6 | result[k] = v 7 | } 8 | return result 9 | } 10 | -------------------------------------------------------------------------------- /pkg/util/copy_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCopyValues(t *testing.T) { 10 | for _, tc := range []struct { 11 | name string 12 | value map[string]interface{} 13 | }{ 14 | { 15 | name: "empty", 16 | value: map[string]interface{}{}, 17 | }, 18 | { 19 | name: "simple", 20 | value: map[string]interface{}{ 21 | "foo": "bar", 22 | }, 23 | }, 24 | { 25 | name: "nested", 26 | value: map[string]interface{}{ 27 | "foo": map[string]interface{}{ 28 | "bar": "baz", 29 | }, 30 | }, 31 | }, 32 | } { 33 | t.Run(tc.name, func(t *testing.T) { 34 | result := CopyValues(tc.value) 35 | require.Equal(t, tc.value, result) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/util/generics.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func Contains[T comparable](s []T, e T) bool { 4 | for _, v := range s { 5 | if v == e { 6 | return true 7 | } 8 | } 9 | return false 10 | } 11 | -------------------------------------------------------------------------------- /provisioner/aws_az.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // SubnetInfo has information about a subnet. 8 | type SubnetInfo struct { 9 | SubnetID string 10 | SubnetIPV6CIDRs []string 11 | } 12 | 13 | // AZInfo tracks information about available AZs based on explicit restrictions or available subnets 14 | type AZInfo struct { 15 | subnets map[string]SubnetInfo 16 | } 17 | 18 | // RestrictAZs returns a new AZInfo that is restricted to provided AZs 19 | func (info *AZInfo) RestrictAZs(availableAZs []string) *AZInfo { 20 | result := make(map[string]SubnetInfo) 21 | 22 | for _, az := range availableAZs { 23 | if subnet, ok := info.subnets[az]; ok { 24 | result[az] = subnet 25 | } 26 | } 27 | 28 | return &AZInfo{subnets: result} 29 | } 30 | 31 | // Subnets returns a map of AZ->subnet that also contains an entry for the virtual '*' AZ 32 | // TODO drop the * 33 | func (info *AZInfo) SubnetsByAZ() map[string]string { 34 | result := make(map[string]string) 35 | for _, az := range info.AvailabilityZones() { 36 | subnet := info.subnets[az] 37 | result[az] = subnet.SubnetID 38 | 39 | if existing, ok := result[subnetAllAZName]; ok { 40 | result[subnetAllAZName] = existing + "," + subnet.SubnetID 41 | } else { 42 | result[subnetAllAZName] = subnet.SubnetID 43 | } 44 | } 45 | return result 46 | } 47 | 48 | // SubnetIPv6CIDRs returns a list of available subnet IPV6 CIDRs. 49 | func (info *AZInfo) SubnetIPv6CIDRs() []string { 50 | var result []string 51 | for _, subnetInfo := range info.subnets { 52 | result = append(result, subnetInfo.SubnetIPV6CIDRs...) 53 | } 54 | return result 55 | } 56 | 57 | // AvailabilityZones returns a list of available AZs 58 | func (info *AZInfo) AvailabilityZones() []string { 59 | var result []string 60 | for az := range info.subnets { 61 | result = append(result, az) 62 | } 63 | sort.Strings(result) 64 | return result 65 | } 66 | -------------------------------------------------------------------------------- /provisioner/aws_az_test.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | var ( 10 | info = &AZInfo{ 11 | subnets: map[string]SubnetInfo{ 12 | "eu-central-1a": { 13 | SubnetID: "subnet-1a", 14 | SubnetIPV6CIDRs: []string{"2001:db8::/64"}, 15 | }, 16 | "eu-central-1b": { 17 | SubnetID: "subnet-1b", 18 | SubnetIPV6CIDRs: []string{"2001:db8::/64"}, 19 | }, 20 | "eu-central-1c": { 21 | SubnetID: "subnet-1c", 22 | SubnetIPV6CIDRs: []string{"2001:db8::/64"}, 23 | }, 24 | }, 25 | } 26 | ) 27 | 28 | func TestSubnetsByAZ(t *testing.T) { 29 | expected := map[string]string{ 30 | "*": "subnet-1a,subnet-1b,subnet-1c", 31 | "eu-central-1a": "subnet-1a", 32 | "eu-central-1b": "subnet-1b", 33 | "eu-central-1c": "subnet-1c", 34 | } 35 | require.Equal(t, expected, info.SubnetsByAZ()) 36 | } 37 | 38 | func TestAvailabilityZones(t *testing.T) { 39 | require.Equal(t, []string{"eu-central-1a", "eu-central-1b", "eu-central-1c"}, info.AvailabilityZones()) 40 | } 41 | 42 | func TestRestrictAZs(t *testing.T) { 43 | restricted := info.RestrictAZs([]string{"eu-central-1b", "eu-central-1d"}) 44 | require.NotEqual(t, info, restricted) 45 | require.Equal(t, map[string]string{"*": "subnet-1b", "eu-central-1b": "subnet-1b"}, restricted.SubnetsByAZ()) 46 | require.Equal(t, []string{"eu-central-1b"}, restricted.AvailabilityZones()) 47 | } 48 | 49 | func TestSubnetIPv6CIDRs(t *testing.T) { 50 | require.Equal(t, []string{"2001:db8::/64", "2001:db8::/64", "2001:db8::/64"}, info.SubnetIPv6CIDRs()) 51 | } 52 | -------------------------------------------------------------------------------- /provisioner/cf.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | type stackPolicyEffect string 4 | type stackPolicyAction string 5 | type stackPolicyPrincipal string 6 | 7 | const ( 8 | stackPolicyEffectDeny stackPolicyEffect = "Deny" 9 | stackPolicyEffectAllow stackPolicyEffect = "Allow" 10 | 11 | stackPolicyActionUpdateModify stackPolicyAction = "Update:Modify" 12 | stackPolicyActionUpdateReplace stackPolicyAction = "Update:Replace" 13 | stackPolicyActionUpdateDelete stackPolicyAction = "Update:Delete" 14 | stackPolicyActionUpdateAll stackPolicyAction = "Update:*" 15 | 16 | stackPolicyPrincipalAll stackPolicyPrincipal = "*" 17 | ) 18 | 19 | type stackPolicyConditionStringEquals struct { 20 | ResourceType []string `json:"ResourceType"` 21 | } 22 | 23 | type stackPolicyCondition struct { 24 | StringEquals stackPolicyConditionStringEquals `json:"StringEquals"` 25 | } 26 | 27 | type stackPolicyStatement struct { 28 | Effect stackPolicyEffect `json:"Effect"` 29 | Action []stackPolicyAction `json:"Action,omitempty"` 30 | Principal stackPolicyPrincipal `json:"Principal"` 31 | Resource []string `json:"Resource,omitempty"` 32 | NotResource []string `json:"NotResource,omitempty"` 33 | Condition *stackPolicyCondition `json:"Condition,omitempty"` 34 | } 35 | 36 | type stackPolicy struct { 37 | Statements []stackPolicyStatement `json:"Statement"` 38 | } 39 | -------------------------------------------------------------------------------- /provisioner/hack.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | // getHostedZone gets derrive hosted zone from an APIServerURL. 10 | func getHostedZone(APIServerURL string) (string, error) { 11 | url, err := url.Parse(APIServerURL) 12 | if err != nil { 13 | return "", err 14 | } 15 | 16 | split := strings.Split(url.Host, ".") 17 | if len(split) < 2 { 18 | return "", fmt.Errorf("can't derive hosted zone from URL %s", APIServerURL) 19 | } 20 | 21 | return strings.Join(split[1:], "."), nil 22 | } 23 | -------------------------------------------------------------------------------- /provisioner/jwks.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/base64" 8 | "encoding/json" 9 | "encoding/pem" 10 | "fmt" 11 | 12 | "github.com/pkg/errors" 13 | "gopkg.in/square/go-jose.v2" 14 | ) 15 | 16 | const ( 17 | kubernetesProgrammaticAuthorization = "urn:kubernetes:programmatic_authorization" 18 | idTokenResponse = "id_token" 19 | publicSubject = "public" 20 | jwksURL = "%s/openid-configuration/keys.json" 21 | subjectClaim = "sub" 22 | issuerClaim = "iss" 23 | jwkUse = "sig" 24 | ) 25 | 26 | // Provider contains the subset of the OpenID Connect provider metadata needed to request 27 | // and verify ID Tokens. 28 | type Provider struct { 29 | Issuer string `json:"issuer"` 30 | AuthURL string `json:"authorization_endpoint"` 31 | JWKSURL string `json:"jwks_uri"` 32 | SupportedResponseTypes []string `json:"response_types_supported"` 33 | SupportedSubjectTypes []string `json:"subject_types_supported"` 34 | AlgorithmsSupported []string `json:"id_token_signing_alg_values_supported"` 35 | SupportedClaims []string `json:"claims_supported"` 36 | } 37 | 38 | // copied from kubernetes/kubernetes#78502 39 | func keyIDFromPublicKey(publicKey interface{}) (string, error) { 40 | publicKeyDERBytes, err := x509.MarshalPKIXPublicKey(publicKey) 41 | if err != nil { 42 | return "", fmt.Errorf("failed to serialize public key to DER format: %v", err) 43 | } 44 | 45 | hasher := crypto.SHA256.New() 46 | _, err = hasher.Write(publicKeyDERBytes) 47 | if err != nil { 48 | return "", fmt.Errorf("failed to write sha256 of public key: %v", err) 49 | } 50 | publicKeyDERHash := hasher.Sum(nil) 51 | 52 | keyID := base64.RawURLEncoding.EncodeToString(publicKeyDERHash) 53 | 54 | return keyID, nil 55 | } 56 | 57 | type KeyResponse struct { 58 | Keys []map[string]string `json:"keys"` 59 | } 60 | 61 | func generateJWKSDocument(serviceAccountKey string) (string, error) { 62 | block, _ := pem.Decode([]byte(serviceAccountKey)) 63 | if block == nil { 64 | return "", errors.Errorf("Error decoding serviceAccountKey") 65 | } 66 | 67 | pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) 68 | if err != nil { 69 | return "", fmt.Errorf("failed to parse public key: %v", err) 70 | } 71 | 72 | var alg jose.SignatureAlgorithm 73 | switch pubKey.(type) { 74 | case *rsa.PublicKey: 75 | alg = jose.RS256 76 | default: 77 | return "", errors.New("Public key was not RSA") 78 | } 79 | 80 | kid, err := keyIDFromPublicKey(pubKey) 81 | if err != nil { 82 | return "", err 83 | } 84 | key := jose.JSONWebKey{ 85 | Key: pubKey, 86 | KeyID: kid, 87 | Algorithm: string(alg), 88 | Use: jwkUse, 89 | } 90 | keys, err := getStringMapList(key) 91 | if err != nil { 92 | return "", fmt.Errorf("failed to get key list: %v", err) 93 | } 94 | keyResponse := KeyResponse{Keys: keys} 95 | response, err := json.MarshalIndent(keyResponse, "", " ") 96 | if err != nil { 97 | return "", fmt.Errorf("failed to marshal key response: %v", err) 98 | } 99 | return string(response), nil 100 | } 101 | 102 | // getStringMapList is needed because the default serializer does not preserve an empty "kid" field 103 | func getStringMapList(key jose.JSONWebKey) ([]map[string]string, error) { 104 | keyJSON, err := json.Marshal(key) 105 | if err != nil { 106 | return nil, fmt.Errorf("failed to marshall key: %v", err) 107 | } 108 | var keyMap map[string]string 109 | err = json.Unmarshal(keyJSON, &keyMap) 110 | if err != nil { 111 | return nil, fmt.Errorf("failed to unmarshall key json: %v", err) 112 | } 113 | keyMapCopy := make(map[string]string, len(keyMap)) 114 | for k, v := range keyMap { 115 | keyMapCopy[k] = v 116 | } 117 | keyMapCopy["kid"] = "" 118 | return []map[string]string{keyMap, keyMapCopy}, nil 119 | } 120 | 121 | func generateOIDCDiscoveryDocument(apiServerURL string) (string, error) { 122 | provider := Provider{ 123 | Issuer: apiServerURL, 124 | AuthURL: kubernetesProgrammaticAuthorization, 125 | JWKSURL: fmt.Sprintf(jwksURL, apiServerURL), 126 | SupportedResponseTypes: []string{idTokenResponse}, 127 | SupportedSubjectTypes: []string{publicSubject}, 128 | AlgorithmsSupported: []string{string(jose.RS256)}, 129 | SupportedClaims: []string{subjectClaim, issuerClaim}, 130 | } 131 | 132 | document, err := json.MarshalIndent(provider, "", " ") 133 | if err != nil { 134 | return "", fmt.Errorf("failed to serialize oidc discovery document: %v", err) 135 | } 136 | return string(document), nil 137 | } 138 | -------------------------------------------------------------------------------- /provisioner/jwks_test.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/base64" 9 | "encoding/binary" 10 | "encoding/json" 11 | "encoding/pem" 12 | "strings" 13 | "testing" 14 | 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | type testDiscoveryDoc map[string][]map[string]string 19 | 20 | func TestGenerateJWKSDocument(t *testing.T) { 21 | key, err := rsa.GenerateKey(rand.Reader, 2048) 22 | require.NoError(t, err) 23 | pubKey, err := x509.MarshalPKIXPublicKey(key.Public()) 24 | require.NoError(t, err) 25 | pemKey := pem.EncodeToMemory(&pem.Block{ 26 | Type: "RSA PUBLIC KEY", 27 | Headers: nil, 28 | Bytes: pubKey, 29 | }) 30 | document, err := generateJWKSDocument(string(pemKey)) 31 | require.NoError(t, err) 32 | var testDoc testDiscoveryDoc 33 | err = json.Unmarshal([]byte(document), &testDoc) 34 | require.NoError(t, err) 35 | require.Contains(t, testDoc, "keys") 36 | keys := testDoc["keys"] 37 | require.Len(t, keys, 2) 38 | firstKey := keys[0] 39 | secondKey := keys[1] 40 | pubKeyModulus, err := base64UrlEncode(key.N.Bytes()) 41 | require.NoError(t, err) 42 | require.Equal(t, firstKey["n"], string(pubKeyModulus)) 43 | require.Equal(t, secondKey["n"], string(pubKeyModulus)) 44 | exponent, err := intToByteArray(uint64(key.E)) 45 | require.NoError(t, err) 46 | pubKeyExp, err := base64UrlEncode(exponent) 47 | require.NoError(t, err) 48 | require.Equal(t, firstKey["e"], string(pubKeyExp)) 49 | require.Equal(t, secondKey["e"], string(pubKeyExp)) 50 | require.Greater(t, len(firstKey["kid"]), 0) 51 | require.Equal(t, len(secondKey["kid"]), 0) 52 | } 53 | 54 | func intToByteArray(input uint64) ([]byte, error) { 55 | bigEndian := make([]byte, 8) 56 | binary.BigEndian.PutUint64(bigEndian, input) 57 | output := bytes.TrimLeft(bigEndian, "\x00") 58 | return output, nil 59 | } 60 | 61 | // base64UrlEncode is special url encoding scheme as per RFC 7518 https://tools.ietf.org/html/rfc7518#page-30 62 | func base64UrlEncode(input []byte) ([]byte, error) { 63 | buffer := bytes.NewBuffer(nil) 64 | encoder := base64.NewEncoder(base64.URLEncoding, buffer) 65 | _, err := encoder.Write(input) 66 | if err != nil { 67 | return nil, err 68 | } 69 | err = encoder.Close() 70 | if err != nil { 71 | return nil, err 72 | } 73 | trimmed := strings.TrimRight(buffer.String(), "=") 74 | return []byte(trimmed), nil 75 | } 76 | -------------------------------------------------------------------------------- /provisioner/kubesystem.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | // Applier defines an interface which given a path can apply manifests to a 4 | // kubernetes cluster. 5 | type Applier interface { 6 | Apply(path string) error 7 | } 8 | -------------------------------------------------------------------------------- /provisioner/provisioner.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | log "github.com/sirupsen/logrus" 8 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 9 | "github.com/zalando-incubator/cluster-lifecycle-manager/channel" 10 | "github.com/zalando-incubator/cluster-lifecycle-manager/config" 11 | ) 12 | 13 | type ( 14 | // Provisioner is an interface describing how to provision or decommission 15 | // clusters. 16 | Provisioner interface { 17 | Supports(cluster *api.Cluster) bool 18 | 19 | Provision( 20 | ctx context.Context, 21 | logger *log.Entry, 22 | cluster *api.Cluster, 23 | channelConfig channel.Config, 24 | ) error 25 | 26 | Decommission( 27 | ctx context.Context, 28 | logger *log.Entry, 29 | cluster *api.Cluster, 30 | ) error 31 | } 32 | 33 | // CreationHook is an interface that provisioners can use while provisioning 34 | // a cluster. 35 | // 36 | // This is useful for example to pass additional configuration only known at 37 | // a later stage of provisioning. For example, when provisioning an EKS 38 | // cluster, the provisioner only knows what is the API Server URL after 39 | // applying the initial CloudFormation. 40 | CreationHook interface { 41 | // Execute performs updates used by a provisioner during cluster 42 | // creation. 43 | Execute( 44 | adapter awsInterface, 45 | cluster *api.Cluster, 46 | ) ( 47 | *HookResponse, 48 | error, 49 | ) 50 | } 51 | 52 | // HookResponse contain configuration parameters that a provisioner can use 53 | // at a later stage. 54 | HookResponse struct { 55 | APIServerURL string 56 | CAData []byte 57 | ServiceCIDR string 58 | } 59 | 60 | // Options is the options that can be passed to a provisioner when initialized. 61 | Options struct { 62 | DryRun bool 63 | ApplyOnly bool 64 | UpdateStrategy config.UpdateStrategy 65 | RemoveVolumes bool 66 | ManageEtcdStack bool 67 | Hook CreationHook 68 | } 69 | ) 70 | 71 | var ( 72 | // ErrProviderNotSupported is the error returned from porvisioners if 73 | // they don't support the cluster provider defined. 74 | ErrProviderNotSupported = errors.New("unsupported provider type") 75 | ) 76 | -------------------------------------------------------------------------------- /provisioner/remote_files.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "fmt" 8 | "path" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/service/kms" 12 | "github.com/aws/aws-sdk-go/service/kms/kmsiface" 13 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 14 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 15 | "github.com/zalando-incubator/cluster-lifecycle-manager/channel" 16 | awsUtils "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/aws" 17 | "gopkg.in/yaml.v2" 18 | ) 19 | 20 | type FilesRenderer struct { 21 | awsAdapter *awsAdapter 22 | cluster *api.Cluster 23 | config channel.Config 24 | directory string 25 | nodePool *api.NodePool 26 | instanceTypes *awsUtils.InstanceTypes 27 | } 28 | 29 | func (f *FilesRenderer) RenderAndUploadFiles( 30 | values map[string]interface{}, 31 | bucketName string, 32 | kmsKey string, 33 | ) (string, error) { 34 | var ( 35 | manifest channel.Manifest 36 | err error 37 | ) 38 | if f.nodePool == nil { 39 | // assume it's etcd 40 | manifest, err = f.config.EtcdManifest(filesTemplateName) 41 | } else { 42 | manifest, err = f.config.NodePoolManifest( 43 | f.nodePool.Profile, 44 | filesTemplateName, 45 | ) 46 | } 47 | if err != nil { 48 | return "", err 49 | } 50 | 51 | rendered, err := renderSingleTemplate( 52 | manifest, 53 | f.cluster, 54 | f.nodePool, 55 | values, 56 | f.awsAdapter, 57 | f.instanceTypes, 58 | ) 59 | if err != nil { 60 | return "", err 61 | } 62 | 63 | archive, err := makeArchive(rendered, kmsKey, f.awsAdapter.kmsClient) 64 | if err != nil { 65 | return "", err 66 | } 67 | 68 | userDataHash, err := generateDataHash([]byte(rendered)) 69 | if err != nil { 70 | return "", fmt.Errorf("failed to generate hash of userdata: %v", err) 71 | } 72 | 73 | filename := path.Join(f.cluster.LocalID, f.directory, userDataHash) 74 | 75 | _, err = f.awsAdapter.s3Uploader.Upload(&s3manager.UploadInput{ 76 | Bucket: aws.String(bucketName), 77 | Key: aws.String(filename), 78 | Body: bytes.NewReader(archive), 79 | }) 80 | if err != nil { 81 | return "", err 82 | } 83 | 84 | return fmt.Sprintf("s3://%s/%s", bucketName, filename), nil 85 | } 86 | 87 | func makeArchive(input string, kmsKey string, kmsClient kmsiface.KMSAPI) ([]byte, error) { 88 | var data remoteData 89 | err := yaml.UnmarshalStrict([]byte(input), &data) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | var filesTar bytes.Buffer 95 | gzw := gzip.NewWriter(&filesTar) 96 | defer gzw.Close() 97 | tarWriter := tar.NewWriter(gzw) 98 | defer tarWriter.Close() 99 | for _, remoteFile := range data.Files { 100 | var compressionInput []byte 101 | var finalPath string 102 | 103 | decoded, err := base64Decode(remoteFile.Data) 104 | if err != nil { 105 | return nil, fmt.Errorf("failed to decode data for file: %s", remoteFile.Path) 106 | } 107 | if remoteFile.Encrypted { 108 | output, err := kmsClient.Encrypt(&kms.EncryptInput{KeyId: &kmsKey, Plaintext: []byte(decoded)}) 109 | if err != nil { 110 | return nil, err 111 | } 112 | compressionInput = output.CiphertextBlob 113 | finalPath = fmt.Sprintf("%s.enc", remoteFile.Path) 114 | } else { 115 | compressionInput = []byte(decoded) 116 | finalPath = remoteFile.Path 117 | } 118 | err = tarWriter.WriteHeader(&tar.Header{ 119 | Name: finalPath, 120 | Size: int64(len(compressionInput)), 121 | Mode: remoteFile.Permissions, 122 | }) 123 | if err != nil { 124 | return nil, fmt.Errorf("failed to write header: %v", err) 125 | } 126 | _, err = tarWriter.Write(compressionInput) 127 | if err != nil { 128 | return nil, fmt.Errorf("failed to write to tar archive: %v", err) 129 | } 130 | } 131 | err = tarWriter.Close() 132 | if err != nil { 133 | return nil, err 134 | } 135 | err = gzw.Close() 136 | if err != nil { 137 | return nil, err 138 | } 139 | return filesTar.Bytes(), nil 140 | } 141 | -------------------------------------------------------------------------------- /provisioner/stdout.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "context" 5 | 6 | log "github.com/sirupsen/logrus" 7 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 8 | "github.com/zalando-incubator/cluster-lifecycle-manager/channel" 9 | ) 10 | 11 | type stdoutProvisioner struct{} 12 | 13 | // NewStdoutProvisioner creates a new provisioner which prints to stdout 14 | // instead of doing any actual provsioning. 15 | func NewStdoutProvisioner() Provisioner { 16 | return &stdoutProvisioner{} 17 | } 18 | 19 | func (p *stdoutProvisioner) Supports(_ *api.Cluster) bool { 20 | return true 21 | } 22 | 23 | // Provision mocks provisioning a cluster. 24 | func (p *stdoutProvisioner) Provision( 25 | _ context.Context, 26 | logger *log.Entry, 27 | cluster *api.Cluster, 28 | _ channel.Config, 29 | ) error { 30 | logger.Infof("stdout: Provisioning cluster %s.", cluster.ID) 31 | 32 | return nil 33 | } 34 | 35 | // Decommission mocks decommissioning a cluster. 36 | func (p *stdoutProvisioner) Decommission(_ context.Context, logger *log.Entry, cluster *api.Cluster) error { 37 | logger.Infof("stdout: Decommissioning cluster %s.", cluster.ID) 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /provisioner/zalando_aws.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | log "github.com/sirupsen/logrus" 9 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 10 | "github.com/zalando-incubator/cluster-lifecycle-manager/channel" 11 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/decrypter" 12 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/util/command" 13 | "golang.org/x/oauth2" 14 | ) 15 | 16 | type ZalandoAWSProvisioner struct { 17 | clusterpyProvisioner 18 | } 19 | 20 | // NewZalandoAWSProvisioner returns a new provisioner by passing its location 21 | // and and IAM role to use. 22 | func NewZalandoAWSProvisioner( 23 | execManager *command.ExecManager, 24 | tokenSource oauth2.TokenSource, 25 | secretDecrypter decrypter.Decrypter, 26 | assumedRole string, 27 | awsConfig *aws.Config, 28 | options *Options, 29 | ) Provisioner { 30 | provisioner := &ZalandoAWSProvisioner{ 31 | clusterpyProvisioner: clusterpyProvisioner{ 32 | awsConfig: awsConfig, 33 | assumedRole: assumedRole, 34 | execManager: execManager, 35 | secretDecrypter: secretDecrypter, 36 | tokenSource: tokenSource, 37 | manageMasterNodes: true, 38 | }, 39 | } 40 | 41 | if options != nil { 42 | provisioner.dryRun = options.DryRun 43 | provisioner.applyOnly = options.ApplyOnly 44 | provisioner.updateStrategy = options.UpdateStrategy 45 | provisioner.removeVolumes = options.RemoveVolumes 46 | provisioner.manageEtcdStack = options.ManageEtcdStack 47 | } 48 | 49 | return provisioner 50 | } 51 | 52 | func (z *ZalandoAWSProvisioner) Supports(cluster *api.Cluster) bool { 53 | return cluster.Provider == api.ZalandoAWSProvider 54 | } 55 | 56 | func (z *ZalandoAWSProvisioner) Provision( 57 | ctx context.Context, 58 | logger *log.Entry, 59 | cluster *api.Cluster, 60 | channelConfig channel.Config, 61 | ) error { 62 | if !z.Supports(cluster) { 63 | return ErrProviderNotSupported 64 | } 65 | 66 | awsAdapter, err := z.setupAWSAdapter(logger, cluster) 67 | if err != nil { 68 | return fmt.Errorf("failed to setup AWS Adapter: %v", err) 69 | } 70 | 71 | logger.Infof( 72 | "clusterpy: Prepare for provisioning cluster %s (%s)..", 73 | cluster.ID, 74 | cluster.LifecycleStatus, 75 | ) 76 | 77 | return z.provision( 78 | ctx, 79 | logger, 80 | awsAdapter, 81 | z.tokenSource, 82 | cluster, 83 | channelConfig, 84 | ) 85 | } 86 | 87 | // Decommission decommissions a cluster provisioned in AWS. 88 | func (z *ZalandoAWSProvisioner) Decommission( 89 | ctx context.Context, 90 | logger *log.Entry, 91 | cluster *api.Cluster, 92 | ) error { 93 | if !z.Supports(cluster) { 94 | return ErrProviderNotSupported 95 | } 96 | 97 | awsAdapter, err := z.setupAWSAdapter(logger, cluster) 98 | if err != nil { 99 | return fmt.Errorf("failed to setup AWS Adapter: %v", err) 100 | } 101 | 102 | return z.decommission(ctx, logger, awsAdapter, z.tokenSource, cluster, nil) 103 | } 104 | -------------------------------------------------------------------------------- /provisioner/zalando_eks.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | log "github.com/sirupsen/logrus" 10 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 11 | "github.com/zalando-incubator/cluster-lifecycle-manager/channel" 12 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/aws/eks" 13 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/decrypter" 14 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/util/command" 15 | "github.com/zalando-incubator/cluster-lifecycle-manager/registry" 16 | ) 17 | 18 | const ( 19 | KeyEKSEndpoint = "eks_endpoint" 20 | KeyEKSCAData = "eks_certificate_authority_data" 21 | KeyEKSOIDCIssuerURL = "eks_oidc_issuer_url" 22 | ) 23 | 24 | type ( 25 | ZalandoEKSProvisioner struct { 26 | clusterpyProvisioner 27 | } 28 | 29 | // ZalandoEKSCreationHook is a hook specific for EKS cluster provisioning. 30 | ZalandoEKSCreationHook struct { 31 | clusterRegistry registry.Registry 32 | } 33 | ) 34 | 35 | // NewZalandoEKSProvisioner returns a new provisioner capable of provisioning 36 | // EKS clusters by passing its location and and IAM role to use. 37 | func NewZalandoEKSProvisioner( 38 | execManager *command.ExecManager, 39 | secretDecrypter decrypter.Decrypter, 40 | assumedRole string, 41 | awsConfig *aws.Config, 42 | options *Options, 43 | ) Provisioner { 44 | provisioner := &ZalandoEKSProvisioner{ 45 | clusterpyProvisioner: clusterpyProvisioner{ 46 | awsConfig: awsConfig, 47 | assumedRole: assumedRole, 48 | execManager: execManager, 49 | secretDecrypter: secretDecrypter, 50 | manageMasterNodes: false, 51 | manageEtcdStack: false, 52 | }, 53 | } 54 | 55 | if options != nil { 56 | provisioner.dryRun = options.DryRun 57 | provisioner.applyOnly = options.ApplyOnly 58 | provisioner.updateStrategy = options.UpdateStrategy 59 | provisioner.removeVolumes = options.RemoveVolumes 60 | provisioner.hook = options.Hook 61 | } 62 | 63 | return provisioner 64 | } 65 | 66 | func (z *ZalandoEKSProvisioner) Supports(cluster *api.Cluster) bool { 67 | return cluster.Provider == api.ZalandoEKSProvider 68 | } 69 | 70 | func (z *ZalandoEKSProvisioner) Provision( 71 | ctx context.Context, 72 | logger *log.Entry, 73 | cluster *api.Cluster, 74 | channelConfig channel.Config, 75 | ) error { 76 | if !z.Supports(cluster) { 77 | return ErrProviderNotSupported 78 | } 79 | 80 | awsAdapter, err := z.setupAWSAdapter(logger, cluster) 81 | if err != nil { 82 | return fmt.Errorf("failed to setup AWS Adapter: %v", err) 83 | } 84 | 85 | eksTokenSource := eks.NewTokenSource(awsAdapter.session, eksID(cluster.ID)) 86 | 87 | logger.Infof( 88 | "clusterpy: Prepare for provisioning EKS cluster %s (%s)..", 89 | cluster.ID, 90 | cluster.LifecycleStatus, 91 | ) 92 | 93 | return z.provision( 94 | ctx, 95 | logger, 96 | awsAdapter, 97 | eksTokenSource, 98 | cluster, 99 | channelConfig, 100 | ) 101 | } 102 | 103 | func (z *ZalandoEKSProvisioner) Decommission( 104 | ctx context.Context, 105 | logger *log.Entry, 106 | cluster *api.Cluster, 107 | ) error { 108 | if !z.Supports(cluster) { 109 | return ErrProviderNotSupported 110 | } 111 | 112 | logger.Infof( 113 | "Decommissioning EKS cluster: %s (%s)", 114 | cluster.Alias, 115 | cluster.ID, 116 | ) 117 | 118 | awsAdapter, err := z.setupAWSAdapter(logger, cluster) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | clusterDetails, err := awsAdapter.GetEKSClusterDetails(cluster) 124 | if err != nil { 125 | return err 126 | } 127 | cluster.APIServerURL = clusterDetails.Endpoint 128 | 129 | caData, err := base64.StdEncoding.DecodeString( 130 | clusterDetails.CertificateAuthority, 131 | ) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | tokenSource := eks.NewTokenSource(awsAdapter.session, eksID(cluster.ID)) 137 | 138 | return z.decommission( 139 | ctx, 140 | logger, 141 | awsAdapter, 142 | tokenSource, 143 | cluster, 144 | caData, 145 | awsAdapter.DeleteLeakedAWSVPCCNIENIs, 146 | ) 147 | } 148 | 149 | // NewZalandoEKSCreationHook returns a new hook for EKS cluster provisioning, 150 | // configured to use the given cluster registry. 151 | func NewZalandoEKSCreationHook( 152 | clusterRegistry registry.Registry, 153 | ) CreationHook { 154 | return &ZalandoEKSCreationHook{ 155 | clusterRegistry: clusterRegistry, 156 | } 157 | } 158 | 159 | // Execute updates the configuration only known after deploying the first 160 | // CloudFormation stack. 161 | // 162 | // The method returns the API server URL, the Certificate Authority data, 163 | // and the subnets. Additionally Execute updates the configured cluster 164 | // registry with the EKS API Server URL and the Certificate Authority data. 165 | func (z *ZalandoEKSCreationHook) Execute( 166 | adapter awsInterface, 167 | cluster *api.Cluster, 168 | ) (*HookResponse, error) { 169 | res := &HookResponse{} 170 | 171 | clusterDetails, err := adapter.GetEKSClusterDetails(cluster) 172 | if err != nil { 173 | return nil, err 174 | } 175 | decodedCA, err := base64.StdEncoding.DecodeString( 176 | clusterDetails.CertificateAuthority, 177 | ) 178 | if err != nil { 179 | return nil, err 180 | } 181 | 182 | if cluster.ConfigItems == nil { 183 | cluster.ConfigItems = map[string]string{} 184 | } 185 | 186 | toUpdate := map[string]string{} 187 | if cluster.ConfigItems[KeyEKSEndpoint] != clusterDetails.Endpoint { 188 | toUpdate[KeyEKSEndpoint] = clusterDetails.Endpoint 189 | } 190 | if cluster.ConfigItems[KeyEKSCAData] != clusterDetails.CertificateAuthority { 191 | toUpdate[KeyEKSCAData] = clusterDetails.CertificateAuthority 192 | } 193 | if cluster.ConfigItems[KeyEKSOIDCIssuerURL] != clusterDetails.OIDCIssuerURL { 194 | toUpdate[KeyEKSOIDCIssuerURL] = clusterDetails.OIDCIssuerURL 195 | } 196 | 197 | err = z.clusterRegistry.UpdateConfigItems(cluster, toUpdate) 198 | if err != nil { 199 | return nil, err 200 | } 201 | 202 | res.APIServerURL = clusterDetails.Endpoint 203 | res.CAData = decodedCA 204 | res.ServiceCIDR = clusterDetails.ServiceCIDR 205 | 206 | return res, nil 207 | } 208 | -------------------------------------------------------------------------------- /provisioner/zalando_eks_test.go: -------------------------------------------------------------------------------- 1 | package provisioner 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 8 | "github.com/zalando-incubator/cluster-lifecycle-manager/registry" 9 | ) 10 | 11 | type ( 12 | mockAWSAdapter struct{} 13 | mockRegistry struct{} 14 | ) 15 | 16 | func (m *mockAWSAdapter) GetEKSClusterDetails(_ *api.Cluster) ( 17 | *EKSClusterDetails, 18 | error, 19 | ) { 20 | return &EKSClusterDetails{ 21 | Endpoint: "https://api.cluster.local", 22 | CertificateAuthority: "YmxhaA==", 23 | OIDCIssuerURL: "https://oidc.provider.local/id/foo", 24 | }, nil 25 | } 26 | 27 | func (r *mockRegistry) ListClusters(_ registry.Filter) ( 28 | []*api.Cluster, 29 | error, 30 | ) { 31 | return []*api.Cluster{}, nil 32 | } 33 | 34 | func (r *mockRegistry) UpdateLifecycleStatus(_ *api.Cluster) error { 35 | return nil 36 | } 37 | 38 | func (r *mockRegistry) UpdateConfigItems(_ *api.Cluster, _ map[string]string) error { 39 | return nil 40 | } 41 | 42 | func TestCreationHookExecute(t *testing.T) { 43 | for _, tc := range []struct { 44 | expected *HookResponse 45 | }{ 46 | { 47 | expected: &HookResponse{ 48 | APIServerURL: "https://api.cluster.local", 49 | CAData: []byte("blah"), 50 | }, 51 | }, 52 | } { 53 | z := NewZalandoEKSCreationHook(&mockRegistry{}) 54 | res, err := z.Execute( 55 | &mockAWSAdapter{}, 56 | &api.Cluster{}, 57 | ) 58 | 59 | require.NoError(t, err) 60 | require.Equal(t, tc.expected, res) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /registry/file.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | log "github.com/sirupsen/logrus" 8 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type fileRegistry struct { 13 | filePath string 14 | } 15 | 16 | // FileRegistryData wrapper around cluster items read from file 17 | type FileRegistryData struct { 18 | Clusters []*api.Cluster `json:"clusters" yaml:"clusters"` 19 | } 20 | 21 | var fileClusters = &FileRegistryData{} //store file content locally 22 | 23 | // NewFileRegistry returns file registry client 24 | func NewFileRegistry(filePath string) Registry { 25 | return &fileRegistry{ 26 | filePath: filePath, 27 | } 28 | } 29 | 30 | func (r *fileRegistry) ListClusters(_ Filter) ([]*api.Cluster, error) { 31 | fileContent, err := os.ReadFile(r.filePath) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | err = yaml.Unmarshal(fileContent, &fileClusters) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | for _, cluster := range fileClusters.Clusters { 42 | for _, nodePool := range cluster.NodePools { 43 | if nodePool.Profile == "worker-karpenter" && len(nodePool.InstanceTypes) == 0 { 44 | nodePool.InstanceType = "" 45 | continue 46 | } 47 | if len(nodePool.InstanceTypes) == 0 { 48 | return nil, fmt.Errorf("no instance types for cluster %s, pool %s", cluster.ID, nodePool.Name) 49 | } 50 | nodePool.InstanceType = nodePool.InstanceTypes[0] 51 | } 52 | } 53 | 54 | return fileClusters.Clusters, nil 55 | } 56 | 57 | func (r *fileRegistry) UpdateLifecycleStatus(cluster *api.Cluster) error { 58 | if cluster == nil { 59 | return fmt.Errorf("failed to update the cluster. Empty cluster is passed") 60 | } 61 | for _, c := range fileClusters.Clusters { 62 | if c.ID == cluster.ID { 63 | log.Debugf("[Cluster %s updated] Lifecycle status: %s", cluster.ID, cluster.LifecycleStatus) 64 | log.Debugf("[Cluster %s updated] Current status: %#v", cluster.ID, *cluster.Status) 65 | return nil 66 | } 67 | } 68 | return fmt.Errorf("failed to update the cluster: cluster %s not found", cluster.ID) 69 | } 70 | 71 | func (r *fileRegistry) UpdateConfigItems( 72 | cluster *api.Cluster, 73 | configItems map[string]string, 74 | ) error { 75 | if cluster == nil { 76 | return fmt.Errorf( 77 | "failed to update the cluster. Empty cluster is passed", 78 | ) 79 | } 80 | for _, c := range fileClusters.Clusters { 81 | if c.ID == cluster.ID { 82 | log.Debugf( 83 | "[Cluster %s updated] Config Items: %v", 84 | cluster.ID, 85 | configItems, 86 | ) 87 | for key, value := range configItems { 88 | cluster.ConfigItems[key] = value 89 | } 90 | return nil 91 | } 92 | } 93 | return fmt.Errorf( 94 | "failed to update the cluster: cluster %s not found", 95 | cluster.ID, 96 | ) 97 | } 98 | -------------------------------------------------------------------------------- /registry/http_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/stretchr/testify/require" 11 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 12 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/models" 13 | "golang.org/x/oauth2" 14 | ) 15 | 16 | const ( 17 | testClusterID = "testcluster" 18 | ) 19 | 20 | func setupRegistry( 21 | clusterInRegistry *models.Cluster, 22 | configKeys []string, 23 | ) *httptest.Server { 24 | mux := http.NewServeMux() 25 | mux.HandleFunc( 26 | "/kubernetes-clusters/"+*clusterInRegistry.ID, 27 | func(res http.ResponseWriter, req *http.Request) { 28 | var clusterUpdate models.ClusterUpdate 29 | if req.Method != http.MethodPatch { 30 | res.WriteHeader(http.StatusMethodNotAllowed) 31 | return 32 | } 33 | 34 | if req.Body == nil { 35 | res.WriteHeader(http.StatusBadRequest) 36 | return 37 | } 38 | defer req.Body.Close() 39 | 40 | if err := json.NewDecoder(req.Body).Decode(&clusterUpdate); err != nil { 41 | res.WriteHeader(http.StatusBadRequest) 42 | return 43 | } 44 | 45 | clusterInRegistry.LifecycleStatus = &clusterUpdate.LifecycleStatus 46 | clusterInRegistry.Status = clusterUpdate.Status 47 | }, 48 | ) 49 | 50 | for _, key := range configKeys { 51 | mux.HandleFunc( 52 | "/kubernetes-clusters/"+*clusterInRegistry.ID+"/config-items/"+key, 53 | func(res http.ResponseWriter, req *http.Request) { 54 | var configValue models.ConfigValue 55 | if req.Method != http.MethodPut { 56 | res.WriteHeader(http.StatusMethodNotAllowed) 57 | return 58 | } 59 | 60 | if req.Body == nil { 61 | res.WriteHeader(http.StatusBadRequest) 62 | return 63 | } 64 | defer req.Body.Close() 65 | 66 | if err := json.NewDecoder(req.Body).Decode( 67 | &configValue, 68 | ); err != nil { 69 | 70 | res.WriteHeader(http.StatusBadRequest) 71 | return 72 | } 73 | 74 | clusterInRegistry.ConfigItems[key] = *configValue.Value 75 | }, 76 | ) 77 | } 78 | 79 | ts := httptest.NewServer(mux) 80 | 81 | return ts 82 | } 83 | 84 | func TestUpdateConfigItems(t *testing.T) { 85 | for _, tc := range []struct { 86 | configItems map[string]string 87 | }{ 88 | { 89 | configItems: map[string]string{ 90 | "foo": "fighters", 91 | }, 92 | }, 93 | { 94 | configItems: map[string]string{ 95 | "eks_endpoint": "https://api.eks.eu-central-1.amazonaws.com", 96 | }, 97 | }, 98 | { 99 | configItems: map[string]string{ 100 | "eks_endpoint": "https://api.eks.eu-central-1.amazonaws.com", 101 | "eks_certificate_authority_data": "YmxhaA==", 102 | "eks_oidc_issuer_url": "https://oidc.eks.eu-central-1.amazonaws.com/id/foo", 103 | }, 104 | }, 105 | } { 106 | clusterInRegistry := &models.Cluster{ 107 | ID: aws.String(testClusterID), 108 | ConfigItems: map[string]string{ 109 | "foo": "bar", 110 | }, 111 | } 112 | 113 | keys := make([]string, 0, len(tc.configItems)) 114 | for key := range tc.configItems { 115 | keys = append(keys, key) 116 | } 117 | ts := setupRegistry(clusterInRegistry, keys) 118 | defer ts.Close() 119 | 120 | registry := NewRegistry( 121 | ts.URL, 122 | oauth2.StaticTokenSource(&oauth2.Token{}), 123 | &Options{}, 124 | ) 125 | if registry == nil { 126 | t.Errorf("failed to create Registry") 127 | continue 128 | } 129 | 130 | err := registry.UpdateConfigItems( 131 | &api.Cluster{ID: testClusterID}, 132 | tc.configItems, 133 | ) 134 | 135 | require.NoError(t, err) 136 | for key := range tc.configItems { 137 | require.Contains(t, clusterInRegistry.ConfigItems, key) 138 | require.Equal(t, tc.configItems[key], clusterInRegistry.ConfigItems[key]) 139 | } 140 | } 141 | } 142 | 143 | func TestUpdateLifeCycleStatus(t *testing.T) { 144 | for _, tc := range []struct { 145 | lifecycleStatus string 146 | status *api.ClusterStatus 147 | }{ 148 | { 149 | lifecycleStatus: "ready", 150 | status: &api.ClusterStatus{ 151 | CurrentVersion: "bbbb", 152 | LastVersion: "xoxo", 153 | NextVersion: "cccc", 154 | }, 155 | }, 156 | { 157 | lifecycleStatus: "decommissioned", 158 | status: &api.ClusterStatus{ 159 | CurrentVersion: "aaaa", 160 | LastVersion: "xoxo", 161 | NextVersion: "bbbb", 162 | }, 163 | }, 164 | } { 165 | clusterInRegistry := &models.Cluster{ 166 | ID: aws.String(testClusterID), 167 | LifecycleStatus: aws.String("provisioning"), 168 | Status: &models.ClusterStatus{ 169 | CurrentVersion: "aaaa", 170 | LastVersion: "xoxo", 171 | NextVersion: "bbbb", 172 | }, 173 | } 174 | 175 | ts := setupRegistry(clusterInRegistry, []string{}) 176 | defer ts.Close() 177 | 178 | registry := NewRegistry( 179 | ts.URL, 180 | oauth2.StaticTokenSource(&oauth2.Token{}), 181 | &Options{}, 182 | ) 183 | if registry == nil { 184 | t.Errorf("failed to create Registry") 185 | continue 186 | } 187 | 188 | err := registry.UpdateLifecycleStatus( 189 | &api.Cluster{ 190 | ID: testClusterID, 191 | LifecycleStatus: tc.lifecycleStatus, 192 | Status: tc.status, 193 | }, 194 | ) 195 | 196 | require.NoError(t, err) 197 | require.Equal(t, tc.lifecycleStatus, *clusterInRegistry.LifecycleStatus) 198 | require.Equal( 199 | t, 200 | tc.status.CurrentVersion, 201 | clusterInRegistry.Status.CurrentVersion, 202 | ) 203 | require.Equal( 204 | t, 205 | tc.status.LastVersion, 206 | clusterInRegistry.Status.LastVersion, 207 | ) 208 | require.Equal( 209 | t, 210 | tc.status.NextVersion, 211 | clusterInRegistry.Status.NextVersion, 212 | ) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "log" 5 | "net/url" 6 | 7 | "github.com/zalando-incubator/cluster-lifecycle-manager/api" 8 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/models" 9 | "golang.org/x/oauth2" 10 | ) 11 | 12 | // Filter defines a filter which can be used when listing clusters. 13 | type Filter struct { 14 | LifecycleStatus *string 15 | Providers []string 16 | } 17 | 18 | // Registry defines an interface for listing and updating clusters from a 19 | // cluster registry. 20 | type Registry interface { 21 | ListClusters(filter Filter) ([]*api.Cluster, error) 22 | UpdateLifecycleStatus(cluster *api.Cluster) error 23 | UpdateConfigItems(cluster *api.Cluster, configItems map[string]string) error 24 | } 25 | 26 | // NewRegistry initializes a new registry source based on the uri. 27 | func NewRegistry(uri string, tokenSource oauth2.TokenSource, options *Options) Registry { 28 | url, err := url.Parse(uri) 29 | if err != nil { 30 | return nil 31 | } 32 | 33 | switch url.Scheme { 34 | case "http", "https": 35 | return NewHTTPRegistry(url, tokenSource, options) 36 | case "file", "": 37 | return NewFileRegistry(url.Host + url.Path) 38 | default: 39 | log.Fatalf("unknown registry type: %v", url.Scheme) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | // Includes returns true if the cluster should be included based on the filter. 46 | // Returns false if the given cluster is nil. 47 | func (f *Filter) Includes(cluster *models.Cluster) bool { 48 | if cluster == nil { 49 | return false 50 | } 51 | 52 | if f.LifecycleStatus != nil && 53 | *cluster.LifecycleStatus != *f.LifecycleStatus { 54 | 55 | return false 56 | } 57 | 58 | if len(f.Providers) == 0 { 59 | return true 60 | } 61 | 62 | for _, p := range f.Providers { 63 | if *cluster.Provider == string(p) { 64 | return true 65 | } 66 | } 67 | 68 | return false 69 | } 70 | -------------------------------------------------------------------------------- /registry/registry_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/zalando-incubator/cluster-lifecycle-manager/pkg/cluster-registry/models" 8 | ) 9 | 10 | func TestFilter(t *testing.T) { 11 | for _, tc := range []struct { 12 | testCase string 13 | lifecycleStatus *string 14 | providers []string 15 | cluster *models.Cluster 16 | expected bool 17 | }{ 18 | { 19 | testCase: "nil cluster", 20 | lifecycleStatus: nil, 21 | providers: nil, 22 | cluster: nil, 23 | expected: false, 24 | }, 25 | { 26 | testCase: "nil filters", 27 | lifecycleStatus: nil, 28 | providers: nil, 29 | cluster: &models.Cluster{ 30 | LifecycleStatus: aws.String("ready"), 31 | Provider: aws.String("zalando-aws"), 32 | }, 33 | expected: true, 34 | }, 35 | { 36 | testCase: "valid lifecycle status", 37 | lifecycleStatus: aws.String("ready"), 38 | providers: nil, 39 | cluster: &models.Cluster{ 40 | LifecycleStatus: aws.String("ready"), 41 | }, 42 | expected: true, 43 | }, 44 | { 45 | testCase: "not valid lifecycle status", 46 | lifecycleStatus: aws.String("ready"), 47 | providers: nil, 48 | cluster: &models.Cluster{ 49 | LifecycleStatus: aws.String("decommissioned"), 50 | }, 51 | expected: false, 52 | }, 53 | { 54 | testCase: "valid provider", 55 | lifecycleStatus: aws.String("ready"), 56 | providers: []string{"zalando-aws"}, 57 | cluster: &models.Cluster{ 58 | LifecycleStatus: aws.String("ready"), 59 | Provider: aws.String("zalando-aws"), 60 | }, 61 | expected: true, 62 | }, 63 | } { 64 | t.Run(tc.testCase, func(t *testing.T) { 65 | f := &Filter{ 66 | LifecycleStatus: tc.lifecycleStatus, 67 | Providers: tc.providers, 68 | } 69 | if f.Includes(tc.cluster) != tc.expected { 70 | t.Errorf("Expected %v, got %v", tc.expected, !tc.expected) 71 | } 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /registry/static.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import "github.com/zalando-incubator/cluster-lifecycle-manager/api" 4 | 5 | type staticRegistry struct{} 6 | 7 | // NewStaticRegistry initializes a new staticRegistry. 8 | func NewStaticRegistry() Registry { 9 | return &staticRegistry{} 10 | } 11 | 12 | func (r *staticRegistry) ListClusters(_ Filter) ([]*api.Cluster, error) { 13 | clusters := []*api.Cluster{ 14 | { 15 | APIServerURL: "http://127.0.0.1:8001", 16 | Channel: "alpha", 17 | ConfigItems: map[string]string{"foo": "bar"}, 18 | CriticalityLevel: 2, 19 | Environment: "dev", 20 | ID: "123", 21 | InfrastructureAccount: "fake:abc", 22 | LifecycleStatus: "ready", 23 | }, 24 | } 25 | 26 | return clusters, nil 27 | } 28 | 29 | func (r *staticRegistry) UpdateLifecycleStatus(_ *api.Cluster) error { 30 | return nil 31 | } 32 | 33 | func (r *staticRegistry) UpdateConfigItems( 34 | _ *api.Cluster, 35 | _ map[string]string, 36 | ) error { 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package main 4 | 5 | import ( 6 | _ "github.com/go-swagger/go-swagger/cmd/swagger" 7 | ) 8 | --------------------------------------------------------------------------------